1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 2 (function(win) { 3 var whiteSpaceRe = /^\s*|\s*$/g, 4 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 5 6 var tinymce = { 7 majorVersion : '3', 8 9 minorVersion : '5.4.1', 10 11 releaseDate : '2012-06-24', 12 13 _init : function() { 14 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 15 16 t.isOpera = win.opera && opera.buildNumber; 17 18 t.isWebKit = /WebKit/.test(ua); 19 20 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); 21 22 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 23 24 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 25 26 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 27 28 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 29 30 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 31 32 t.isMac = ua.indexOf('Mac') != -1; 33 34 t.isAir = /adobeair/i.test(ua); 35 36 t.isIDevice = /(iPad|iPhone)/.test(ua); 37 38 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 39 40 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 41 if (win.tinyMCEPreInit) { 42 t.suffix = tinyMCEPreInit.suffix; 43 t.baseURL = tinyMCEPreInit.base; 44 t.query = tinyMCEPreInit.query; 45 return; 46 } 47 48 // Get suffix and base 49 t.suffix = ''; 50 51 // If base element found, add that infront of baseURL 52 nl = d.getElementsByTagName('base'); 53 for (i=0; i<nl.length; i++) { 54 v = nl[i].href; 55 if (v) { 56 // Host only value like http://site.com or http://site.com:8008 57 if (/^https?:\/\/[^\/]+$/.test(v)) 58 v += '/'; 59 60 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 61 } 62 } 63 64 function getBase(n) { 65 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 66 if (/_(src|dev)\.js/g.test(n.src)) 67 t.suffix = '_src'; 68 69 if ((p = n.src.indexOf('?')) != -1) 70 t.query = n.src.substring(p + 1); 71 72 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 73 74 // If path to script is relative and a base href was found add that one infront 75 // the src property will always be an absolute one on non IE browsers and IE 8 76 // so this logic will basically only be executed on older IE versions 77 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 78 t.baseURL = base + t.baseURL; 79 80 return t.baseURL; 81 } 82 83 return null; 84 }; 85 86 // Check document 87 nl = d.getElementsByTagName('script'); 88 for (i=0; i<nl.length; i++) { 89 if (getBase(nl[i])) 90 return; 91 } 92 93 // Check head 94 n = d.getElementsByTagName('head')[0]; 95 if (n) { 96 nl = n.getElementsByTagName('script'); 97 for (i=0; i<nl.length; i++) { 98 if (getBase(nl[i])) 99 return; 100 } 101 } 102 103 return; 104 }, 105 106 is : function(o, t) { 107 if (!t) 108 return o !== undef; 109 110 if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) 111 return true; 112 113 return typeof(o) == t; 114 }, 115 116 makeMap : function(items, delim, map) { 117 var i; 118 119 items = items || []; 120 delim = delim || ','; 121 122 if (typeof(items) == "string") 123 items = items.split(delim); 124 125 map = map || {}; 126 127 i = items.length; 128 while (i--) 129 map[items[i]] = {}; 130 131 return map; 132 }, 133 134 each : function(o, cb, s) { 135 var n, l; 136 137 if (!o) 138 return 0; 139 140 s = s || o; 141 142 if (o.length !== undef) { 143 // Indexed arrays, needed for Safari 144 for (n=0, l = o.length; n < l; n++) { 145 if (cb.call(s, o[n], n, o) === false) 146 return 0; 147 } 148 } else { 149 // Hashtables 150 for (n in o) { 151 if (o.hasOwnProperty(n)) { 152 if (cb.call(s, o[n], n, o) === false) 153 return 0; 154 } 155 } 156 } 157 158 return 1; 159 }, 160 161 162 map : function(a, f) { 163 var o = []; 164 165 tinymce.each(a, function(v) { 166 o.push(f(v)); 167 }); 168 169 return o; 170 }, 171 172 grep : function(a, f) { 173 var o = []; 174 175 tinymce.each(a, function(v) { 176 if (!f || f(v)) 177 o.push(v); 178 }); 179 180 return o; 181 }, 182 183 inArray : function(a, v) { 184 var i, l; 185 186 if (a) { 187 for (i = 0, l = a.length; i < l; i++) { 188 if (a[i] === v) 189 return i; 190 } 191 } 192 193 return -1; 194 }, 195 196 extend : function(obj, ext) { 197 var i, l, name, args = arguments, value; 198 199 for (i = 1, l = args.length; i < l; i++) { 200 ext = args[i]; 201 for (name in ext) { 202 if (ext.hasOwnProperty(name)) { 203 value = ext[name]; 204 205 if (value !== undef) { 206 obj[name] = value; 207 } 208 } 209 } 210 } 211 212 return obj; 213 }, 214 215 216 trim : function(s) { 217 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 218 }, 219 220 create : function(s, p, root) { 221 var t = this, sp, ns, cn, scn, c, de = 0; 222 223 // Parse : <prefix> <class>:<super class> 224 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 225 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 226 227 // Create namespace for new class 228 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 229 230 // Class already exists 231 if (ns[cn]) 232 return; 233 234 // Make pure static class 235 if (s[2] == 'static') { 236 ns[cn] = p; 237 238 if (this.onCreate) 239 this.onCreate(s[2], s[3], ns[cn]); 240 241 return; 242 } 243 244 // Create default constructor 245 if (!p[cn]) { 246 p[cn] = function() {}; 247 de = 1; 248 } 249 250 // Add constructor and methods 251 ns[cn] = p[cn]; 252 t.extend(ns[cn].prototype, p); 253 254 // Extend 255 if (s[5]) { 256 sp = t.resolve(s[5]).prototype; 257 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 258 259 // Extend constructor 260 c = ns[cn]; 261 if (de) { 262 // Add passthrough constructor 263 ns[cn] = function() { 264 return sp[scn].apply(this, arguments); 265 }; 266 } else { 267 // Add inherit constructor 268 ns[cn] = function() { 269 this.parent = sp[scn]; 270 return c.apply(this, arguments); 271 }; 272 } 273 ns[cn].prototype[cn] = ns[cn]; 274 275 // Add super methods 276 t.each(sp, function(f, n) { 277 ns[cn].prototype[n] = sp[n]; 278 }); 279 280 // Add overridden methods 281 t.each(p, function(f, n) { 282 // Extend methods if needed 283 if (sp[n]) { 284 ns[cn].prototype[n] = function() { 285 this.parent = sp[n]; 286 return f.apply(this, arguments); 287 }; 288 } else { 289 if (n != cn) 290 ns[cn].prototype[n] = f; 291 } 292 }); 293 } 294 295 // Add static methods 296 t.each(p['static'], function(f, n) { 297 ns[cn][n] = f; 298 }); 299 300 if (this.onCreate) 301 this.onCreate(s[2], s[3], ns[cn].prototype); 302 }, 303 304 walk : function(o, f, n, s) { 305 s = s || this; 306 307 if (o) { 308 if (n) 309 o = o[n]; 310 311 tinymce.each(o, function(o, i) { 312 if (f.call(s, o, i, n) === false) 313 return false; 314 315 tinymce.walk(o, f, n, s); 316 }); 317 } 318 }, 319 320 createNS : function(n, o) { 321 var i, v; 322 323 o = o || win; 324 325 n = n.split('.'); 326 for (i=0; i<n.length; i++) { 327 v = n[i]; 328 329 if (!o[v]) 330 o[v] = {}; 331 332 o = o[v]; 333 } 334 335 return o; 336 }, 337 338 resolve : function(n, o) { 339 var i, l; 340 341 o = o || win; 342 343 n = n.split('.'); 344 for (i = 0, l = n.length; i < l; i++) { 345 o = o[n[i]]; 346 347 if (!o) 348 break; 349 } 350 351 return o; 352 }, 353 354 addUnload : function(f, s) { 355 var t = this, unload; 356 357 unload = function() { 358 var li = t.unloads, o, n; 359 360 if (li) { 361 // Call unload handlers 362 for (n in li) { 363 o = li[n]; 364 365 if (o && o.func) 366 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 367 } 368 369 // Detach unload function 370 if (win.detachEvent) { 371 win.detachEvent('onbeforeunload', fakeUnload); 372 win.detachEvent('onunload', unload); 373 } else if (win.removeEventListener) 374 win.removeEventListener('unload', unload, false); 375 376 // Destroy references 377 t.unloads = o = li = w = unload = 0; 378 379 // Run garbarge collector on IE 380 if (win.CollectGarbage) 381 CollectGarbage(); 382 } 383 }; 384 385 function fakeUnload() { 386 var d = document; 387 388 function stop() { 389 // Prevent memory leak 390 d.detachEvent('onstop', stop); 391 392 // Call unload handler 393 if (unload) 394 unload(); 395 396 d = 0; 397 }; 398 399 // Is there things still loading, then do some magic 400 if (d.readyState == 'interactive') { 401 // Fire unload when the currently loading page is stopped 402 if (d) 403 d.attachEvent('onstop', stop); 404 405 // Remove onstop listener after a while to prevent the unload function 406 // to execute if the user presses cancel in an onbeforeunload 407 // confirm dialog and then presses the browser stop button 408 win.setTimeout(function() { 409 if (d) 410 d.detachEvent('onstop', stop); 411 }, 0); 412 } 413 }; 414 415 f = {func : f, scope : s || this}; 416 417 if (!t.unloads) { 418 // Attach unload handler 419 if (win.attachEvent) { 420 win.attachEvent('onunload', unload); 421 win.attachEvent('onbeforeunload', fakeUnload); 422 } else if (win.addEventListener) 423 win.addEventListener('unload', unload, false); 424 425 // Setup initial unload handler array 426 t.unloads = [f]; 427 } else 428 t.unloads.push(f); 429 430 return f; 431 }, 432 433 removeUnload : function(f) { 434 var u = this.unloads, r = null; 435 436 tinymce.each(u, function(o, i) { 437 if (o && o.func == f) { 438 u.splice(i, 1); 439 r = f; 440 return false; 441 } 442 }); 443 444 return r; 445 }, 446 447 explode : function(s, d) { 448 if (!s || tinymce.is(s, 'array')) { 449 return s; 450 } 451 452 return tinymce.map(s.split(d || ','), tinymce.trim); 453 }, 454 455 _addVer : function(u) { 456 var v; 457 458 if (!this.query) 459 return u; 460 461 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 462 463 if (u.indexOf('#') == -1) 464 return u + v; 465 466 return u.replace('#', v + '#'); 467 }, 468 469 // Fix function for IE 9 where regexps isn't working correctly 470 // Todo: remove me once MS fixes the bug 471 _replace : function(find, replace, str) { 472 // On IE9 we have to fake $x replacement 473 if (isRegExpBroken) { 474 return str.replace(find, function() { 475 var val = replace, args = arguments, i; 476 477 for (i = 0; i < args.length - 2; i++) { 478 if (args[i] === undef) { 479 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 480 } else { 481 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 482 } 483 } 484 485 return val; 486 }); 487 } 488 489 return str.replace(find, replace); 490 } 491 492 }; 493 494 // Initialize the API 495 tinymce._init(); 496 497 // Expose tinymce namespace to the global namespace (window) 498 win.tinymce = win.tinyMCE = tinymce; 499 500 // Describe the different namespaces 501 502 })(window); 503 504 505 506 (function() { 507 if (!window.Prototype) 508 return alert("Load prototype first!"); 509 510 // Patch in core NS functions 511 tinymce.extend(tinymce, { 512 trim : function(s) {return s ? s.strip() : '';}, 513 inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;} 514 }); 515 516 // Patch in functions in various clases 517 // Add a "#ifndefjquery" statement around each core API function you add below 518 var patches = { 519 'tinymce.util.JSON' : { 520 /*serialize : function(o) { 521 return o.toJSON(); 522 }*/ 523 } 524 }; 525 526 // Patch functions after a class is created 527 tinymce.onCreate = function(ty, c, p) { 528 tinymce.extend(p, patches[c]); 529 }; 530 })(); 531 532 533 tinymce.create('tinymce.util.Dispatcher', { 534 scope : null, 535 listeners : null, 536 inDispatch: false, 537 538 Dispatcher : function(scope) { 539 this.scope = scope || this; 540 this.listeners = []; 541 }, 542 543 add : function(callback, scope) { 544 this.listeners.push({cb : callback, scope : scope || this.scope}); 545 546 return callback; 547 }, 548 549 addToTop : function(callback, scope) { 550 var self = this, listener = {cb : callback, scope : scope || self.scope}; 551 552 // Create new listeners if addToTop is executed in a dispatch loop 553 if (self.inDispatch) { 554 self.listeners = [listener].concat(self.listeners); 555 } else { 556 self.listeners.unshift(listener); 557 } 558 559 return callback; 560 }, 561 562 remove : function(callback) { 563 var listeners = this.listeners, output = null; 564 565 tinymce.each(listeners, function(listener, i) { 566 if (callback == listener.cb) { 567 output = listener; 568 listeners.splice(i, 1); 569 return false; 570 } 571 }); 572 573 return output; 574 }, 575 576 dispatch : function() { 577 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 578 579 self.inDispatch = true; 580 581 // Needs to be a real loop since the listener count might change while looping 582 // And this is also more efficient 583 for (i = 0; i < listeners.length; i++) { 584 listener = listeners[i]; 585 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 586 587 if (returnValue === false) 588 break; 589 } 590 591 self.inDispatch = false; 592 593 return returnValue; 594 } 595 596 }); 597 598 (function() { 599 var each = tinymce.each; 600 601 tinymce.create('tinymce.util.URI', { 602 URI : function(u, s) { 603 var t = this, o, a, b, base_url; 604 605 // Trim whitespace 606 u = tinymce.trim(u); 607 608 // Default settings 609 s = t.settings = s || {}; 610 611 // Strange app protocol that isn't http/https or local anchor 612 // For example: mailto,skype,tel etc. 613 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 614 t.source = u; 615 return; 616 } 617 618 // Absolute path with no host, fake host and protocol 619 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 620 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 621 622 // Relative path http:// or protocol relative //path 623 if (!/^[\w\-]*:?\/\//.test(u)) { 624 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 625 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 626 } 627 628 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 629 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 630 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 631 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 632 var s = u[i]; 633 634 // Zope 3 workaround, they use @@something 635 if (s) 636 s = s.replace(/\(mce_at\)/g, '@@'); 637 638 t[v] = s; 639 }); 640 641 b = s.base_uri; 642 if (b) { 643 if (!t.protocol) 644 t.protocol = b.protocol; 645 646 if (!t.userInfo) 647 t.userInfo = b.userInfo; 648 649 if (!t.port && t.host === 'mce_host') 650 t.port = b.port; 651 652 if (!t.host || t.host === 'mce_host') 653 t.host = b.host; 654 655 t.source = ''; 656 } 657 658 //t.path = t.path || '/'; 659 }, 660 661 setPath : function(p) { 662 var t = this; 663 664 p = /^(.*?)\/?(\w+)?$/.exec(p); 665 666 // Update path parts 667 t.path = p[0]; 668 t.directory = p[1]; 669 t.file = p[2]; 670 671 // Rebuild source 672 t.source = ''; 673 t.getURI(); 674 }, 675 676 toRelative : function(u) { 677 var t = this, o; 678 679 if (u === "./") 680 return u; 681 682 u = new tinymce.util.URI(u, {base_uri : t}); 683 684 // Not on same domain/port or protocol 685 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 686 return u.getURI(); 687 688 var tu = t.getURI(), uu = u.getURI(); 689 690 // Allow usage of the base_uri when relative_urls = true 691 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 692 return tu; 693 694 o = t.toRelPath(t.path, u.path); 695 696 // Add query 697 if (u.query) 698 o += '?' + u.query; 699 700 // Add anchor 701 if (u.anchor) 702 o += '#' + u.anchor; 703 704 return o; 705 }, 706 707 toAbsolute : function(u, nh) { 708 u = new tinymce.util.URI(u, {base_uri : this}); 709 710 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 711 }, 712 713 toRelPath : function(base, path) { 714 var items, bp = 0, out = '', i, l; 715 716 // Split the paths 717 base = base.substring(0, base.lastIndexOf('/')); 718 base = base.split('/'); 719 items = path.split('/'); 720 721 if (base.length >= items.length) { 722 for (i = 0, l = base.length; i < l; i++) { 723 if (i >= items.length || base[i] != items[i]) { 724 bp = i + 1; 725 break; 726 } 727 } 728 } 729 730 if (base.length < items.length) { 731 for (i = 0, l = items.length; i < l; i++) { 732 if (i >= base.length || base[i] != items[i]) { 733 bp = i + 1; 734 break; 735 } 736 } 737 } 738 739 if (bp === 1) 740 return path; 741 742 for (i = 0, l = base.length - (bp - 1); i < l; i++) 743 out += "../"; 744 745 for (i = bp - 1, l = items.length; i < l; i++) { 746 if (i != bp - 1) 747 out += "/" + items[i]; 748 else 749 out += items[i]; 750 } 751 752 return out; 753 }, 754 755 toAbsPath : function(base, path) { 756 var i, nb = 0, o = [], tr, outPath; 757 758 // Split paths 759 tr = /\/$/.test(path) ? '/' : ''; 760 base = base.split('/'); 761 path = path.split('/'); 762 763 // Remove empty chunks 764 each(base, function(k) { 765 if (k) 766 o.push(k); 767 }); 768 769 base = o; 770 771 // Merge relURLParts chunks 772 for (i = path.length - 1, o = []; i >= 0; i--) { 773 // Ignore empty or . 774 if (path[i].length === 0 || path[i] === ".") 775 continue; 776 777 // Is parent 778 if (path[i] === '..') { 779 nb++; 780 continue; 781 } 782 783 // Move up 784 if (nb > 0) { 785 nb--; 786 continue; 787 } 788 789 o.push(path[i]); 790 } 791 792 i = base.length - nb; 793 794 // If /a/b/c or / 795 if (i <= 0) 796 outPath = o.reverse().join('/'); 797 else 798 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 799 800 // Add front / if it's needed 801 if (outPath.indexOf('/') !== 0) 802 outPath = '/' + outPath; 803 804 // Add traling / if it's needed 805 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 806 outPath += tr; 807 808 return outPath; 809 }, 810 811 getURI : function(nh) { 812 var s, t = this; 813 814 // Rebuild source 815 if (!t.source || nh) { 816 s = ''; 817 818 if (!nh) { 819 if (t.protocol) 820 s += t.protocol + '://'; 821 822 if (t.userInfo) 823 s += t.userInfo + '@'; 824 825 if (t.host) 826 s += t.host; 827 828 if (t.port) 829 s += ':' + t.port; 830 } 831 832 if (t.path) 833 s += t.path; 834 835 if (t.query) 836 s += '?' + t.query; 837 838 if (t.anchor) 839 s += '#' + t.anchor; 840 841 t.source = s; 842 } 843 844 return t.source; 845 } 846 }); 847 })(); 848 849 (function() { 850 var each = tinymce.each; 851 852 tinymce.create('static tinymce.util.Cookie', { 853 getHash : function(n) { 854 var v = this.get(n), h; 855 856 if (v) { 857 each(v.split('&'), function(v) { 858 v = v.split('='); 859 h = h || {}; 860 h[unescape(v[0])] = unescape(v[1]); 861 }); 862 } 863 864 return h; 865 }, 866 867 setHash : function(n, v, e, p, d, s) { 868 var o = ''; 869 870 each(v, function(v, k) { 871 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 872 }); 873 874 this.set(n, o, e, p, d, s); 875 }, 876 877 get : function(n) { 878 var c = document.cookie, e, p = n + "=", b; 879 880 // Strict mode 881 if (!c) 882 return; 883 884 b = c.indexOf("; " + p); 885 886 if (b == -1) { 887 b = c.indexOf(p); 888 889 if (b !== 0) 890 return null; 891 } else 892 b += 2; 893 894 e = c.indexOf(";", b); 895 896 if (e == -1) 897 e = c.length; 898 899 return unescape(c.substring(b + p.length, e)); 900 }, 901 902 set : function(n, v, e, p, d, s) { 903 document.cookie = n + "=" + escape(v) + 904 ((e) ? "; expires=" + e.toGMTString() : "") + 905 ((p) ? "; path=" + escape(p) : "") + 906 ((d) ? "; domain=" + d : "") + 907 ((s) ? "; secure" : ""); 908 }, 909 910 remove : function(name, path, domain) { 911 var date = new Date(); 912 913 date.setTime(date.getTime() - 1000); 914 915 this.set(name, '', date, path, domain); 916 } 917 }); 918 })(); 919 920 (function() { 921 function serialize(o, quote) { 922 var i, v, t, name; 923 924 quote = quote || '"'; 925 926 if (o == null) 927 return 'null'; 928 929 t = typeof o; 930 931 if (t == 'string') { 932 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 933 934 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 935 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 936 if (quote === '"' && a === "'") 937 return a; 938 939 i = v.indexOf(b); 940 941 if (i + 1) 942 return '\\' + v.charAt(i + 1); 943 944 a = b.charCodeAt().toString(16); 945 946 return '\\u' + '0000'.substring(a.length) + a; 947 }) + quote; 948 } 949 950 if (t == 'object') { 951 if (o.hasOwnProperty && o instanceof Array) { 952 for (i=0, v = '['; i<o.length; i++) 953 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 954 955 return v + ']'; 956 } 957 958 v = '{'; 959 960 for (name in o) { 961 if (o.hasOwnProperty(name)) { 962 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 963 } 964 } 965 966 return v + '}'; 967 } 968 969 return '' + o; 970 }; 971 972 tinymce.util.JSON = { 973 serialize: serialize, 974 975 parse: function(s) { 976 try { 977 return eval('(' + s + ')'); 978 } catch (ex) { 979 // Ignore 980 } 981 } 982 983 }; 984 })(); 985 986 tinymce.create('static tinymce.util.XHR', { 987 send : function(o) { 988 var x, t, w = window, c = 0; 989 990 function ready() { 991 if (!o.async || x.readyState == 4 || c++ > 10000) { 992 if (o.success && c < 10000 && x.status == 200) 993 o.success.call(o.success_scope, '' + x.responseText, x, o); 994 else if (o.error) 995 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 996 997 x = null; 998 } else 999 w.setTimeout(ready, 10); 1000 }; 1001 1002 // Default settings 1003 o.scope = o.scope || this; 1004 o.success_scope = o.success_scope || o.scope; 1005 o.error_scope = o.error_scope || o.scope; 1006 o.async = o.async === false ? false : true; 1007 o.data = o.data || ''; 1008 1009 function get(s) { 1010 x = 0; 1011 1012 try { 1013 x = new ActiveXObject(s); 1014 } catch (ex) { 1015 } 1016 1017 return x; 1018 }; 1019 1020 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 1021 1022 if (x) { 1023 if (x.overrideMimeType) 1024 x.overrideMimeType(o.content_type); 1025 1026 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1027 1028 if (o.content_type) 1029 x.setRequestHeader('Content-Type', o.content_type); 1030 1031 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1032 1033 x.send(o.data); 1034 1035 // Syncronous request 1036 if (!o.async) 1037 return ready(); 1038 1039 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1040 t = w.setTimeout(ready, 10); 1041 } 1042 } 1043 }); 1044 1045 (function() { 1046 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1047 1048 tinymce.create('tinymce.util.JSONRequest', { 1049 JSONRequest : function(s) { 1050 this.settings = extend({ 1051 }, s); 1052 this.count = 0; 1053 }, 1054 1055 send : function(o) { 1056 var ecb = o.error, scb = o.success; 1057 1058 o = extend(this.settings, o); 1059 1060 o.success = function(c, x) { 1061 c = JSON.parse(c); 1062 1063 if (typeof(c) == 'undefined') { 1064 c = { 1065 error : 'JSON Parse error.' 1066 }; 1067 } 1068 1069 if (c.error) 1070 ecb.call(o.error_scope || o.scope, c.error, x); 1071 else 1072 scb.call(o.success_scope || o.scope, c.result); 1073 }; 1074 1075 o.error = function(ty, x) { 1076 if (ecb) 1077 ecb.call(o.error_scope || o.scope, ty, x); 1078 }; 1079 1080 o.data = JSON.serialize({ 1081 id : o.id || 'c' + (this.count++), 1082 method : o.method, 1083 params : o.params 1084 }); 1085 1086 // JSON content type for Ruby on rails. Bug: #1883287 1087 o.content_type = 'application/json'; 1088 1089 XHR.send(o); 1090 }, 1091 1092 'static' : { 1093 sendRPC : function(o) { 1094 return new tinymce.util.JSONRequest().send(o); 1095 } 1096 } 1097 }); 1098 }()); 1099 (function(tinymce){ 1100 tinymce.VK = { 1101 BACKSPACE: 8, 1102 DELETE: 46, 1103 DOWN: 40, 1104 ENTER: 13, 1105 LEFT: 37, 1106 RIGHT: 39, 1107 SPACEBAR: 32, 1108 TAB: 9, 1109 UP: 38, 1110 1111 modifierPressed: function (e) { 1112 return e.shiftKey || e.ctrlKey || e.altKey; 1113 }, 1114 1115 metaKeyPressed: function(e) { 1116 return tinymce.isMac ? e.metaKey : e.ctrlKey; 1117 } 1118 }; 1119 })(tinymce); 1120 1121 tinymce.util.Quirks = function(editor) { 1122 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; 1123 1124 function setEditorCommandState(cmd, state) { 1125 try { 1126 editor.getDoc().execCommand(cmd, false, state); 1127 } catch (ex) { 1128 // Ignore 1129 } 1130 } 1131 1132 function getDocumentMode() { 1133 var documentMode = editor.getDoc().documentMode; 1134 1135 return documentMode ? documentMode : 6; 1136 }; 1137 1138 function cleanupStylesWhenDeleting() { 1139 function removeMergedFormatSpans(isDelete) { 1140 var rng, blockElm, node, clonedSpan; 1141 1142 rng = selection.getRng(); 1143 1144 // Find root block 1145 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1146 1147 // On delete clone the root span of the next block element 1148 if (isDelete) 1149 blockElm = dom.getNext(blockElm, dom.isBlock); 1150 1151 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace 1152 if (blockElm) { 1153 node = blockElm.firstChild; 1154 1155 // Ignore empty text nodes 1156 while (node && node.nodeType == 3 && node.nodeValue.length === 0) 1157 node = node.nextSibling; 1158 1159 if (node && node.nodeName === 'SPAN') { 1160 clonedSpan = node.cloneNode(false); 1161 } 1162 } 1163 1164 // Do the backspace/delete action 1165 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1166 1167 // Find all odd apple-style-spans 1168 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1169 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { 1170 var bm = selection.getBookmark(); 1171 1172 if (clonedSpan) { 1173 dom.replace(clonedSpan.cloneNode(false), span, true); 1174 } else { 1175 dom.remove(span, true); 1176 } 1177 1178 // Restore the selection 1179 selection.moveToBookmark(bm); 1180 }); 1181 }; 1182 1183 editor.onKeyDown.add(function(editor, e) { 1184 var isDelete; 1185 1186 isDelete = e.keyCode == DELETE; 1187 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1188 e.preventDefault(); 1189 removeMergedFormatSpans(isDelete); 1190 } 1191 }); 1192 1193 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1194 }; 1195 1196 function emptyEditorWhenDeleting() { 1197 function serializeRng(rng) { 1198 var body = dom.create("body"); 1199 var contents = rng.cloneContents(); 1200 body.appendChild(contents); 1201 return selection.serializer.serialize(body, {format: 'html'}); 1202 } 1203 1204 function allContentsSelected(rng) { 1205 var selection = serializeRng(rng); 1206 1207 var allRng = dom.createRng(); 1208 allRng.selectNode(editor.getBody()); 1209 1210 var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection); 1211 return selection === allSelection; 1212 } 1213 1214 editor.onKeyDown.add(function(editor, e) { 1215 var keyCode = e.keyCode, isCollapsed; 1216 1217 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1218 if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { 1219 isCollapsed = editor.selection.isCollapsed(); 1220 1221 // Selection is collapsed but the editor isn't empty 1222 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1223 return; 1224 } 1225 1226 // IE deletes all contents correctly when everything is selected 1227 if (tinymce.isIE && !isCollapsed) { 1228 return; 1229 } 1230 1231 // Selection isn't collapsed but not all the contents is selected 1232 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1233 return; 1234 } 1235 1236 // Manually empty the editor 1237 editor.setContent(''); 1238 editor.selection.setCursorLocation(editor.getBody(), 0); 1239 editor.nodeChanged(); 1240 } 1241 }); 1242 }; 1243 1244 function selectAll() { 1245 editor.onKeyDown.add(function(editor, e) { 1246 if (e.keyCode == 65 && VK.metaKeyPressed(e)) { 1247 e.preventDefault(); 1248 editor.execCommand('SelectAll'); 1249 } 1250 }); 1251 }; 1252 1253 function inputMethodFocus() { 1254 if (!editor.settings.content_editable) { 1255 // Case 1 IME doesn't initialize if you focus the document 1256 dom.bind(editor.getDoc(), 'focusin', function(e) { 1257 selection.setRng(selection.getRng()); 1258 }); 1259 1260 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1261 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1262 if (e.target == editor.getDoc().documentElement) { 1263 editor.getWin().focus(); 1264 selection.setRng(selection.getRng()); 1265 } 1266 }); 1267 } 1268 }; 1269 1270 function removeHrOnBackspace() { 1271 editor.onKeyDown.add(function(editor, e) { 1272 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1273 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1274 var node = selection.getNode(); 1275 var previousSibling = node.previousSibling; 1276 1277 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1278 dom.remove(previousSibling); 1279 tinymce.dom.Event.cancel(e); 1280 } 1281 } 1282 } 1283 }) 1284 } 1285 1286 function focusBody() { 1287 // Fix for a focus bug in FF 3.x where the body element 1288 // wouldn't get proper focus if the user clicked on the HTML element 1289 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1290 editor.onMouseDown.add(function(editor, e) { 1291 if (e.target.nodeName === "HTML") { 1292 var body = editor.getBody(); 1293 1294 // Blur the body it's focused but not correctly focused 1295 body.blur(); 1296 1297 // Refocus the body after a little while 1298 setTimeout(function() { 1299 body.focus(); 1300 }, 0); 1301 } 1302 }); 1303 } 1304 }; 1305 1306 function selectControlElements() { 1307 editor.onClick.add(function(editor, e) { 1308 e = e.target; 1309 1310 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1311 // WebKit can't even do simple things like selecting an image 1312 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1313 if (/^(IMG|HR)$/.test(e.nodeName)) { 1314 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1315 } 1316 1317 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1318 selection.select(e); 1319 } 1320 1321 editor.nodeChanged(); 1322 }); 1323 }; 1324 1325 function removeStylesWhenDeletingAccrossBlockElements() { 1326 function getAttributeApplyFunction() { 1327 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1328 1329 return function() { 1330 var target = selection.getStart(); 1331 1332 if (target !== editor.getBody()) { 1333 dom.setAttrib(target, "style", null); 1334 1335 tinymce.each(template, function(attr) { 1336 target.setAttributeNode(attr.cloneNode(true)); 1337 }); 1338 } 1339 }; 1340 } 1341 1342 function isSelectionAcrossElements() { 1343 return !selection.isCollapsed() && selection.getStart() != selection.getEnd(); 1344 } 1345 1346 function blockEvent(editor, e) { 1347 e.preventDefault(); 1348 return false; 1349 } 1350 1351 editor.onKeyPress.add(function(editor, e) { 1352 var applyAttributes; 1353 1354 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1355 applyAttributes = getAttributeApplyFunction(); 1356 editor.getDoc().execCommand('delete', false, null); 1357 applyAttributes(); 1358 e.preventDefault(); 1359 return false; 1360 } 1361 }); 1362 1363 dom.bind(editor.getDoc(), 'cut', function(e) { 1364 var applyAttributes; 1365 1366 if (isSelectionAcrossElements()) { 1367 applyAttributes = getAttributeApplyFunction(); 1368 editor.onKeyUp.addToTop(blockEvent); 1369 1370 setTimeout(function() { 1371 applyAttributes(); 1372 editor.onKeyUp.remove(blockEvent); 1373 }, 0); 1374 } 1375 }); 1376 } 1377 1378 function selectionChangeNodeChanged() { 1379 var lastRng, selectionTimer; 1380 1381 dom.bind(editor.getDoc(), 'selectionchange', function() { 1382 if (selectionTimer) { 1383 clearTimeout(selectionTimer); 1384 selectionTimer = 0; 1385 } 1386 1387 selectionTimer = window.setTimeout(function() { 1388 var rng = selection.getRng(); 1389 1390 // Compare the ranges to see if it was a real change or not 1391 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1392 editor.nodeChanged(); 1393 lastRng = rng; 1394 } 1395 }, 50); 1396 }); 1397 } 1398 1399 function ensureBodyHasRoleApplication() { 1400 document.body.setAttribute("role", "application"); 1401 } 1402 1403 function disableBackspaceIntoATable() { 1404 editor.onKeyDown.add(function(editor, e) { 1405 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1406 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1407 var previousSibling = selection.getNode().previousSibling; 1408 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1409 return tinymce.dom.Event.cancel(e); 1410 } 1411 } 1412 } 1413 }) 1414 } 1415 1416 function addNewLinesBeforeBrInPre() { 1417 // IE8+ rendering mode does the right thing with BR in PRE 1418 if (getDocumentMode() > 7) { 1419 return; 1420 } 1421 1422 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1423 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1424 setEditorCommandState('RespectVisibilityInDesign', true); 1425 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1426 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1427 1428 // Adds a \n before all BR elements in PRE to get them visual 1429 editor.parser.addNodeFilter('pre', function(nodes, name) { 1430 var i = nodes.length, brNodes, j, brElm, sibling; 1431 1432 while (i--) { 1433 brNodes = nodes[i].getAll('br'); 1434 j = brNodes.length; 1435 while (j--) { 1436 brElm = brNodes[j]; 1437 1438 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1439 sibling = brElm.prev; 1440 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1441 sibling.value += '\n'; 1442 } else { 1443 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1444 } 1445 } 1446 } 1447 }); 1448 1449 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1450 editor.serializer.addNodeFilter('pre', function(nodes, name) { 1451 var i = nodes.length, brNodes, j, brElm, sibling; 1452 1453 while (i--) { 1454 brNodes = nodes[i].getAll('br'); 1455 j = brNodes.length; 1456 while (j--) { 1457 brElm = brNodes[j]; 1458 sibling = brElm.prev; 1459 if (sibling && sibling.type == 3) { 1460 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1461 } 1462 } 1463 } 1464 }); 1465 } 1466 1467 function removePreSerializedStylesWhenSelectingControls() { 1468 dom.bind(editor.getBody(), 'mouseup', function(e) { 1469 var value, node = selection.getNode(); 1470 1471 // Moved styles to attributes on IMG eements 1472 if (node.nodeName == 'IMG') { 1473 // Convert style width to width attribute 1474 if (value = dom.getStyle(node, 'width')) { 1475 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1476 dom.setStyle(node, 'width', ''); 1477 } 1478 1479 // Convert style height to height attribute 1480 if (value = dom.getStyle(node, 'height')) { 1481 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1482 dom.setStyle(node, 'height', ''); 1483 } 1484 } 1485 }); 1486 } 1487 1488 function keepInlineElementOnDeleteBackspace() { 1489 editor.onKeyDown.add(function(editor, e) { 1490 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1491 1492 isDelete = e.keyCode == DELETE; 1493 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1494 rng = selection.getRng(); 1495 container = rng.startContainer; 1496 offset = rng.startOffset; 1497 collapsed = rng.collapsed; 1498 1499 // Override delete if the start container is a text node and is at the beginning of text or 1500 // just before/after the last character to be deleted in collapsed mode 1501 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1502 nonEmptyElements = editor.schema.getNonEmptyElements(); 1503 1504 // Prevent default logic since it's broken 1505 e.preventDefault(); 1506 1507 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1508 brElm = dom.create('br', {id: '__tmp'}); 1509 container.parentNode.insertBefore(brElm, container); 1510 1511 // Do the browser delete 1512 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1513 1514 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1515 container = selection.getRng().startContainer; 1516 sibling = container.previousSibling; 1517 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1518 dom.remove(sibling); 1519 } 1520 1521 // Remove the temp element we inserted 1522 dom.remove('__tmp'); 1523 } 1524 } 1525 }); 1526 } 1527 1528 function removeBlockQuoteOnBackSpace() { 1529 // Add block quote deletion handler 1530 editor.onKeyDown.add(function(editor, e) { 1531 var rng, container, offset, root, parent; 1532 1533 if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { 1534 return; 1535 } 1536 1537 rng = selection.getRng(); 1538 container = rng.startContainer; 1539 offset = rng.startOffset; 1540 root = dom.getRoot(); 1541 parent = container; 1542 1543 if (!rng.collapsed || offset !== 0) { 1544 return; 1545 } 1546 1547 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1548 parent = parent.parentNode; 1549 } 1550 1551 // Is the cursor at the beginning of a blockquote? 1552 if (parent.tagName === 'BLOCKQUOTE') { 1553 // Remove the blockquote 1554 editor.formatter.toggle('blockquote', null, parent); 1555 1556 // Move the caret to the beginning of container 1557 rng.setStart(container, 0); 1558 rng.setEnd(container, 0); 1559 selection.setRng(rng); 1560 selection.collapse(false); 1561 } 1562 }); 1563 }; 1564 1565 function setGeckoEditingOptions() { 1566 function setOpts() { 1567 editor._refreshContentEditable(); 1568 1569 setEditorCommandState("StyleWithCSS", false); 1570 setEditorCommandState("enableInlineTableEditing", false); 1571 1572 if (!settings.object_resizing) { 1573 setEditorCommandState("enableObjectResizing", false); 1574 } 1575 }; 1576 1577 if (!settings.readonly) { 1578 editor.onBeforeExecCommand.add(setOpts); 1579 editor.onMouseDown.add(setOpts); 1580 } 1581 }; 1582 1583 function addBrAfterLastLinks() { 1584 function fixLinks(editor, o) { 1585 tinymce.each(dom.select('a'), function(node) { 1586 var parentNode = node.parentNode, root = dom.getRoot(); 1587 1588 if (parentNode.lastChild === node) { 1589 while (parentNode && !dom.isBlock(parentNode)) { 1590 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1591 return; 1592 } 1593 1594 parentNode = parentNode.parentNode; 1595 } 1596 1597 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1598 } 1599 }); 1600 }; 1601 1602 editor.onExecCommand.add(function(editor, cmd) { 1603 if (cmd === 'CreateLink') { 1604 fixLinks(editor); 1605 } 1606 }); 1607 1608 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1609 }; 1610 1611 function setDefaultBlockType() { 1612 if (settings.forced_root_block) { 1613 editor.onInit.add(function() { 1614 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1615 }); 1616 } 1617 } 1618 1619 function removeGhostSelection() { 1620 function repaint(sender, args) { 1621 if (!sender || !args.initial) { 1622 editor.execCommand('mceRepaint'); 1623 } 1624 }; 1625 1626 editor.onUndo.add(repaint); 1627 editor.onRedo.add(repaint); 1628 editor.onSetContent.add(repaint); 1629 }; 1630 1631 function deleteControlItemOnBackSpace() { 1632 editor.onKeyDown.add(function(editor, e) { 1633 var rng; 1634 1635 if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { 1636 rng = editor.getDoc().selection.createRange(); 1637 if (rng && rng.item) { 1638 e.preventDefault(); 1639 editor.undoManager.beforeChange(); 1640 dom.remove(rng.item(0)); 1641 editor.undoManager.add(); 1642 } 1643 } 1644 }); 1645 }; 1646 1647 function renderEmptyBlocksFix() { 1648 var emptyBlocksCSS; 1649 1650 // IE10+ 1651 if (getDocumentMode() >= 10) { 1652 emptyBlocksCSS = ''; 1653 tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1654 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1655 }); 1656 1657 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1658 } 1659 }; 1660 1661 function fakeImageResize() { 1662 var mouseDownImg, startX, startY, startW, startH; 1663 1664 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1665 return; 1666 } 1667 1668 editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); 1669 1670 function resizeImage(e) { 1671 var deltaX, deltaY, ratio, width, height; 1672 1673 if (mouseDownImg) { 1674 deltaX = e.screenX - startX; 1675 deltaY = e.screenY - startY; 1676 ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); 1677 1678 // Only update styles if the user draged one pixel or more 1679 if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { 1680 // Constrain proportions 1681 width = Math.round(startW * ratio); 1682 height = Math.round(startH * ratio); 1683 1684 // Resize by using style or attribute 1685 if (mouseDownImg.style.width) { 1686 dom.setStyle(mouseDownImg, 'width', width); 1687 } else { 1688 dom.setAttrib(mouseDownImg, 'width', width); 1689 } 1690 1691 // Resize by using style or attribute 1692 if (mouseDownImg.style.height) { 1693 dom.setStyle(mouseDownImg, 'height', height); 1694 } else { 1695 dom.setAttrib(mouseDownImg, 'height', height); 1696 } 1697 1698 if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { 1699 dom.addClass(editor.getBody(), 'mceResizeImages'); 1700 } 1701 } 1702 } 1703 }; 1704 1705 editor.onMouseDown.add(function(editor, e) { 1706 var target = e.target; 1707 1708 if (target.nodeName == "IMG") { 1709 mouseDownImg = target; 1710 startX = e.screenX; 1711 startY = e.screenY; 1712 startW = mouseDownImg.clientWidth; 1713 startH = mouseDownImg.clientHeight; 1714 dom.bind(editor.getDoc(), 'mousemove', resizeImage); 1715 e.preventDefault(); 1716 } 1717 }); 1718 1719 // Unbind events on node change and restore resize cursor 1720 editor.onNodeChange.add(function() { 1721 if (mouseDownImg) { 1722 mouseDownImg = null; 1723 dom.unbind(editor.getDoc(), 'mousemove', resizeImage); 1724 } 1725 1726 if (selection.getNode().nodeName == "IMG") { 1727 dom.addClass(editor.getBody(), 'mceResizeImages'); 1728 } else { 1729 dom.removeClass(editor.getBody(), 'mceResizeImages'); 1730 } 1731 }); 1732 }; 1733 1734 // All browsers 1735 disableBackspaceIntoATable(); 1736 removeBlockQuoteOnBackSpace(); 1737 emptyEditorWhenDeleting(); 1738 1739 // WebKit 1740 if (tinymce.isWebKit) { 1741 keepInlineElementOnDeleteBackspace(); 1742 cleanupStylesWhenDeleting(); 1743 inputMethodFocus(); 1744 selectControlElements(); 1745 setDefaultBlockType(); 1746 1747 // iOS 1748 if (tinymce.isIDevice) { 1749 selectionChangeNodeChanged(); 1750 } else { 1751 fakeImageResize(); 1752 selectAll(); 1753 } 1754 } 1755 1756 // IE 1757 if (tinymce.isIE) { 1758 removeHrOnBackspace(); 1759 ensureBodyHasRoleApplication(); 1760 addNewLinesBeforeBrInPre(); 1761 removePreSerializedStylesWhenSelectingControls(); 1762 deleteControlItemOnBackSpace(); 1763 renderEmptyBlocksFix(); 1764 } 1765 1766 // Gecko 1767 if (tinymce.isGecko) { 1768 removeHrOnBackspace(); 1769 focusBody(); 1770 removeStylesWhenDeletingAccrossBlockElements(); 1771 setGeckoEditingOptions(); 1772 addBrAfterLastLinks(); 1773 removeGhostSelection(); 1774 } 1775 }; 1776 (function(tinymce) { 1777 var namedEntities, baseEntities, reverseEntities, 1778 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1779 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1780 rawCharsRegExp = /[<>&\"\']/g, 1781 entityRegExp = /&(#x|#)?([\w]+);/g, 1782 asciiMap = { 1783 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 1784 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 1785 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 1786 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 1787 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 1788 }; 1789 1790 // Raw entities 1791 baseEntities = { 1792 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 1793 "'" : ''', 1794 '<' : '<', 1795 '>' : '>', 1796 '&' : '&' 1797 }; 1798 1799 // Reverse lookup table for raw entities 1800 reverseEntities = { 1801 '<' : '<', 1802 '>' : '>', 1803 '&' : '&', 1804 '"' : '"', 1805 ''' : "'" 1806 }; 1807 1808 // Decodes text by using the browser 1809 function nativeDecode(text) { 1810 var elm; 1811 1812 elm = document.createElement("div"); 1813 elm.innerHTML = text; 1814 1815 return elm.textContent || elm.innerText || text; 1816 }; 1817 1818 // Build a two way lookup table for the entities 1819 function buildEntitiesLookup(items, radix) { 1820 var i, chr, entity, lookup = {}; 1821 1822 if (items) { 1823 items = items.split(','); 1824 radix = radix || 10; 1825 1826 // Build entities lookup table 1827 for (i = 0; i < items.length; i += 2) { 1828 chr = String.fromCharCode(parseInt(items[i], radix)); 1829 1830 // Only add non base entities 1831 if (!baseEntities[chr]) { 1832 entity = '&' + items[i + 1] + ';'; 1833 lookup[chr] = entity; 1834 lookup[entity] = chr; 1835 } 1836 } 1837 1838 return lookup; 1839 } 1840 }; 1841 1842 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 1843 namedEntities = buildEntitiesLookup( 1844 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 1845 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 1846 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 1847 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 1848 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 1849 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 1850 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 1851 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 1852 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 1853 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 1854 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 1855 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 1856 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 1857 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 1858 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 1859 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 1860 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 1861 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 1862 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 1863 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 1864 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 1865 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 1866 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 1867 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 1868 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 1869 1870 tinymce.html = tinymce.html || {}; 1871 1872 tinymce.html.Entities = { 1873 encodeRaw : function(text, attr) { 1874 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1875 return baseEntities[chr] || chr; 1876 }); 1877 }, 1878 1879 encodeAllRaw : function(text) { 1880 return ('' + text).replace(rawCharsRegExp, function(chr) { 1881 return baseEntities[chr] || chr; 1882 }); 1883 }, 1884 1885 encodeNumeric : function(text, attr) { 1886 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1887 // Multi byte sequence convert it to a single entity 1888 if (chr.length > 1) 1889 return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 1890 1891 return baseEntities[chr] || '' + chr.charCodeAt(0) + ';'; 1892 }); 1893 }, 1894 1895 encodeNamed : function(text, attr, entities) { 1896 entities = entities || namedEntities; 1897 1898 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1899 return baseEntities[chr] || entities[chr] || chr; 1900 }); 1901 }, 1902 1903 getEncodeFunc : function(name, entities) { 1904 var Entities = tinymce.html.Entities; 1905 1906 entities = buildEntitiesLookup(entities) || namedEntities; 1907 1908 function encodeNamedAndNumeric(text, attr) { 1909 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1910 return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr; 1911 }); 1912 }; 1913 1914 function encodeCustomNamed(text, attr) { 1915 return Entities.encodeNamed(text, attr, entities); 1916 }; 1917 1918 // Replace + with , to be compatible with previous TinyMCE versions 1919 name = tinymce.makeMap(name.replace(/\+/g, ',')); 1920 1921 // Named and numeric encoder 1922 if (name.named && name.numeric) 1923 return encodeNamedAndNumeric; 1924 1925 // Named encoder 1926 if (name.named) { 1927 // Custom names 1928 if (entities) 1929 return encodeCustomNamed; 1930 1931 return Entities.encodeNamed; 1932 } 1933 1934 // Numeric 1935 if (name.numeric) 1936 return Entities.encodeNumeric; 1937 1938 // Raw encoder 1939 return Entities.encodeRaw; 1940 }, 1941 1942 decode : function(text) { 1943 return text.replace(entityRegExp, function(all, numeric, value) { 1944 if (numeric) { 1945 value = parseInt(value, numeric.length === 2 ? 16 : 10); 1946 1947 // Support upper UTF 1948 if (value > 0xFFFF) { 1949 value -= 0x10000; 1950 1951 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 1952 } else 1953 return asciiMap[value] || String.fromCharCode(value); 1954 } 1955 1956 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 1957 }); 1958 } 1959 }; 1960 })(tinymce); 1961 1962 tinymce.html.Styles = function(settings, schema) { 1963 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 1964 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 1965 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 1966 trimRightRegExp = /\s+$/, 1967 urlColorRegExp = /rgb/, 1968 undef, i, encodingLookup = {}, encodingItems; 1969 1970 settings = settings || {}; 1971 1972 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 1973 for (i = 0; i < encodingItems.length; i++) { 1974 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 1975 encodingLookup['\uFEFF' + i] = encodingItems[i]; 1976 } 1977 1978 function toHex(match, r, g, b) { 1979 function hex(val) { 1980 val = parseInt(val).toString(16); 1981 1982 return val.length > 1 ? val : '0' + val; // 0 -> 00 1983 }; 1984 1985 return '#' + hex(r) + hex(g) + hex(b); 1986 }; 1987 1988 return { 1989 toHex : function(color) { 1990 return color.replace(rgbRegExp, toHex); 1991 }, 1992 1993 parse : function(css) { 1994 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 1995 1996 function compress(prefix, suffix) { 1997 var top, right, bottom, left; 1998 1999 // Get values and check it it needs compressing 2000 top = styles[prefix + '-top' + suffix]; 2001 if (!top) 2002 return; 2003 2004 right = styles[prefix + '-right' + suffix]; 2005 if (top != right) 2006 return; 2007 2008 bottom = styles[prefix + '-bottom' + suffix]; 2009 if (right != bottom) 2010 return; 2011 2012 left = styles[prefix + '-left' + suffix]; 2013 if (bottom != left) 2014 return; 2015 2016 // Compress 2017 styles[prefix + suffix] = left; 2018 delete styles[prefix + '-top' + suffix]; 2019 delete styles[prefix + '-right' + suffix]; 2020 delete styles[prefix + '-bottom' + suffix]; 2021 delete styles[prefix + '-left' + suffix]; 2022 }; 2023 2024 function canCompress(key) { 2025 var value = styles[key], i; 2026 2027 if (!value || value.indexOf(' ') < 0) 2028 return; 2029 2030 value = value.split(' '); 2031 i = value.length; 2032 while (i--) { 2033 if (value[i] !== value[0]) 2034 return false; 2035 } 2036 2037 styles[key] = value[0]; 2038 2039 return true; 2040 }; 2041 2042 function compress2(target, a, b, c) { 2043 if (!canCompress(a)) 2044 return; 2045 2046 if (!canCompress(b)) 2047 return; 2048 2049 if (!canCompress(c)) 2050 return; 2051 2052 // Compress 2053 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2054 delete styles[a]; 2055 delete styles[b]; 2056 delete styles[c]; 2057 }; 2058 2059 // Encodes the specified string by replacing all \" \' ; : with _<num> 2060 function encode(str) { 2061 isEncoded = true; 2062 2063 return encodingLookup[str]; 2064 }; 2065 2066 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2067 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2068 function decode(str, keep_slashes) { 2069 if (isEncoded) { 2070 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2071 return encodingLookup[str]; 2072 }); 2073 } 2074 2075 if (!keep_slashes) 2076 str = str.replace(/\\([\'\";:])/g, "$1"); 2077 2078 return str; 2079 }; 2080 2081 function processUrl(match, url, url2, url3, str, str2) { 2082 str = str || str2; 2083 2084 if (str) { 2085 str = decode(str); 2086 2087 // Force strings into single quote format 2088 return "'" + str.replace(/\'/g, "\\'") + "'"; 2089 } 2090 2091 url = decode(url || url2 || url3); 2092 2093 // Convert the URL to relative/absolute depending on config 2094 if (urlConverter) 2095 url = urlConverter.call(urlConverterScope, url, 'style'); 2096 2097 // Output new URL format 2098 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2099 }; 2100 2101 if (css) { 2102 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2103 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2104 return str.replace(/[;:]/g, encode); 2105 }); 2106 2107 // Parse styles 2108 while (matches = styleRegExp.exec(css)) { 2109 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2110 value = matches[2].replace(trimRightRegExp, ''); 2111 2112 if (name && value.length > 0) { 2113 // Opera will produce 700 instead of bold in their style values 2114 if (name === 'font-weight' && value === '700') 2115 value = 'bold'; 2116 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2117 value = value.toLowerCase(); 2118 2119 // Convert RGB colors to HEX 2120 value = value.replace(rgbRegExp, toHex); 2121 2122 // Convert URLs and force them into url('value') format 2123 value = value.replace(urlOrStrRegExp, processUrl); 2124 styles[name] = isEncoded ? decode(value, true) : value; 2125 } 2126 2127 styleRegExp.lastIndex = matches.index + matches[0].length; 2128 } 2129 2130 // Compress the styles to reduce it's size for example IE will expand styles 2131 compress("border", ""); 2132 compress("border", "-width"); 2133 compress("border", "-color"); 2134 compress("border", "-style"); 2135 compress("padding", ""); 2136 compress("margin", ""); 2137 compress2('border', 'border-width', 'border-style', 'border-color'); 2138 2139 // Remove pointless border, IE produces these 2140 if (styles.border === 'medium none') 2141 delete styles.border; 2142 } 2143 2144 return styles; 2145 }, 2146 2147 serialize : function(styles, element_name) { 2148 var css = '', name, value; 2149 2150 function serializeStyles(name) { 2151 var styleList, i, l, value; 2152 2153 styleList = schema.styles[name]; 2154 if (styleList) { 2155 for (i = 0, l = styleList.length; i < l; i++) { 2156 name = styleList[i]; 2157 value = styles[name]; 2158 2159 if (value !== undef && value.length > 0) 2160 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2161 } 2162 } 2163 }; 2164 2165 // Serialize styles according to schema 2166 if (element_name && schema && schema.styles) { 2167 // Serialize global styles and element specific styles 2168 serializeStyles('*'); 2169 serializeStyles(element_name); 2170 } else { 2171 // Output the styles in the order they are inside the object 2172 for (name in styles) { 2173 value = styles[name]; 2174 2175 if (value !== undef && value.length > 0) 2176 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2177 } 2178 } 2179 2180 return css; 2181 } 2182 }; 2183 }; 2184 2185 (function(tinymce) { 2186 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2187 2188 function split(str, delim) { 2189 return str.split(delim || ','); 2190 }; 2191 2192 function unpack(lookup, data) { 2193 var key, elements = {}; 2194 2195 function replace(value) { 2196 return value.replace(/[A-Z]+/g, function(key) { 2197 return replace(lookup[key]); 2198 }); 2199 }; 2200 2201 // Unpack lookup 2202 for (key in lookup) { 2203 if (lookup.hasOwnProperty(key)) 2204 lookup[key] = replace(lookup[key]); 2205 } 2206 2207 // Unpack and parse data into object map 2208 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2209 attributes = split(attributes, '|'); 2210 2211 elements[name] = { 2212 attributes : makeMap(attributes), 2213 attributesOrder : attributes, 2214 children : makeMap(children, '|', {'#comment' : {}}) 2215 } 2216 }); 2217 2218 return elements; 2219 }; 2220 2221 function getHTML5() { 2222 var html5 = mapCache.html5; 2223 2224 if (!html5) { 2225 html5 = mapCache.html5 = unpack({ 2226 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2227 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2228 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2229 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2230 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2231 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2232 }, 'html[A|manifest][body|head]' + 2233 'head[A][base|command|link|meta|noscript|script|style|title]' + 2234 'title[A][#]' + 2235 'base[A|href|target][]' + 2236 'link[A|href|rel|media|type|sizes][]' + 2237 'meta[A|http-equiv|name|content|charset][]' + 2238 'style[A|type|media|scoped][#]' + 2239 'script[A|charset|type|src|defer|async][#]' + 2240 'noscript[A][C]' + 2241 'body[A][C]' + 2242 'section[A][C]' + 2243 'nav[A][C]' + 2244 'article[A][C]' + 2245 'aside[A][C]' + 2246 'h1[A][B]' + 2247 'h2[A][B]' + 2248 'h3[A][B]' + 2249 'h4[A][B]' + 2250 'h5[A][B]' + 2251 'h6[A][B]' + 2252 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2253 'header[A][C]' + 2254 'footer[A][C]' + 2255 'address[A][C]' + 2256 'p[A][B]' + 2257 'br[A][]' + 2258 'pre[A][B]' + 2259 'dialog[A][dd|dt]' + 2260 'blockquote[A|cite][C]' + 2261 'ol[A|start|reversed][li]' + 2262 'ul[A][li]' + 2263 'li[A|value][C]' + 2264 'dl[A][dd|dt]' + 2265 'dt[A][B]' + 2266 'dd[A][C]' + 2267 'a[A|href|target|ping|rel|media|type][B]' + 2268 'em[A][B]' + 2269 'strong[A][B]' + 2270 'small[A][B]' + 2271 'cite[A][B]' + 2272 'q[A|cite][B]' + 2273 'dfn[A][B]' + 2274 'abbr[A][B]' + 2275 'code[A][B]' + 2276 'var[A][B]' + 2277 'samp[A][B]' + 2278 'kbd[A][B]' + 2279 'sub[A][B]' + 2280 'sup[A][B]' + 2281 'i[A][B]' + 2282 'b[A][B]' + 2283 'mark[A][B]' + 2284 'progress[A|value|max][B]' + 2285 'meter[A|value|min|max|low|high|optimum][B]' + 2286 'time[A|datetime][B]' + 2287 'ruby[A][B|rt|rp]' + 2288 'rt[A][B]' + 2289 'rp[A][B]' + 2290 'bdo[A][B]' + 2291 'span[A][B]' + 2292 'ins[A|cite|datetime][B]' + 2293 'del[A|cite|datetime][B]' + 2294 'figure[A][C|legend|figcaption]' + 2295 'figcaption[A][C]' + 2296 'img[A|alt|src|height|width|usemap|ismap][]' + 2297 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2298 'embed[A|src|height|width|type][]' + 2299 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2300 'param[A|name|value][]' + 2301 'details[A|open][C|legend]' + 2302 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2303 'menu[A|type|label][C|li]' + 2304 'legend[A][C|B]' + 2305 'div[A][C]' + 2306 'source[A|src|type|media][]' + 2307 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2308 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2309 'hr[A][]' + 2310 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2311 'fieldset[A|disabled|form|name][C|legend]' + 2312 'label[A|form|for][B]' + 2313 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2314 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2315 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2316 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2317 'datalist[A][B|option]' + 2318 'optgroup[A|disabled|label][option]' + 2319 'option[A|disabled|selected|label|value][]' + 2320 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2321 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2322 'output[A|for|form|name][B]' + 2323 'canvas[A|width|height][]' + 2324 'map[A|name][B|C]' + 2325 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2326 'mathml[A][]' + 2327 'svg[A][]' + 2328 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2329 'caption[A][C]' + 2330 'colgroup[A|span][col]' + 2331 'col[A|span][]' + 2332 'thead[A][tr]' + 2333 'tfoot[A][tr]' + 2334 'tbody[A][tr]' + 2335 'tr[A][th|td]' + 2336 'th[A|headers|rowspan|colspan|scope][B]' + 2337 'td[A|headers|rowspan|colspan][C]' + 2338 'wbr[A][]' 2339 ); 2340 } 2341 2342 return html5; 2343 }; 2344 2345 function getHTML4() { 2346 var html4 = mapCache.html4; 2347 2348 if (!html4) { 2349 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2350 html4 = mapCache.html4 = unpack({ 2351 Z : 'H|K|N|O|P', 2352 Y : 'X|form|R|Q', 2353 ZG : 'E|span|width|align|char|charoff|valign', 2354 X : 'p|T|div|U|W|isindex|fieldset|table', 2355 ZF : 'E|align|char|charoff|valign', 2356 W : 'pre|hr|blockquote|address|center|noframes', 2357 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2358 ZD : '[E][S]', 2359 U : 'ul|ol|dl|menu|dir', 2360 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2361 T : 'h1|h2|h3|h4|h5|h6', 2362 ZB : 'X|S|Q', 2363 S : 'R|P', 2364 ZA : 'a|G|J|M|O|P', 2365 R : 'a|H|K|N|O', 2366 Q : 'noscript|P', 2367 P : 'ins|del|script', 2368 O : 'input|select|textarea|label|button', 2369 N : 'M|L', 2370 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2371 L : 'sub|sup', 2372 K : 'J|I', 2373 J : 'tt|i|b|u|s|strike', 2374 I : 'big|small|font|basefont', 2375 H : 'G|F', 2376 G : 'br|span|bdo', 2377 F : 'object|applet|img|map|iframe', 2378 E : 'A|B|C', 2379 D : 'accesskey|tabindex|onfocus|onblur', 2380 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2381 B : 'lang|xml:lang|dir', 2382 A : 'id|class|style|title' 2383 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2384 'style[B|id|type|media|title|xml:space][]' + 2385 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2386 'param[id|name|value|valuetype|type][]' + 2387 'p[E|align][#|S]' + 2388 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2389 'br[A|clear][]' + 2390 'span[E][#|S]' + 2391 'bdo[A|C|B][#|S]' + 2392 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2393 'h1[E|align][#|S]' + 2394 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2395 'map[B|C|A|name][X|form|Q|area]' + 2396 'h2[E|align][#|S]' + 2397 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2398 'h3[E|align][#|S]' + 2399 'tt[E][#|S]' + 2400 'i[E][#|S]' + 2401 'b[E][#|S]' + 2402 'u[E][#|S]' + 2403 's[E][#|S]' + 2404 'strike[E][#|S]' + 2405 'big[E][#|S]' + 2406 'small[E][#|S]' + 2407 'font[A|B|size|color|face][#|S]' + 2408 'basefont[id|size|color|face][]' + 2409 'em[E][#|S]' + 2410 'strong[E][#|S]' + 2411 'dfn[E][#|S]' + 2412 'code[E][#|S]' + 2413 'q[E|cite][#|S]' + 2414 'samp[E][#|S]' + 2415 'kbd[E][#|S]' + 2416 'var[E][#|S]' + 2417 'cite[E][#|S]' + 2418 'abbr[E][#|S]' + 2419 'acronym[E][#|S]' + 2420 'sub[E][#|S]' + 2421 'sup[E][#|S]' + 2422 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2423 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2424 'optgroup[E|disabled|label][option]' + 2425 'option[E|selected|disabled|label|value][]' + 2426 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2427 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2428 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2429 'h4[E|align][#|S]' + 2430 'ins[E|cite|datetime][#|Y]' + 2431 'h5[E|align][#|S]' + 2432 'del[E|cite|datetime][#|Y]' + 2433 'h6[E|align][#|S]' + 2434 'div[E|align][#|Y]' + 2435 'ul[E|type|compact][li]' + 2436 'li[E|type|value][#|Y]' + 2437 'ol[E|type|compact|start][li]' + 2438 'dl[E|compact][dt|dd]' + 2439 'dt[E][#|S]' + 2440 'dd[E][#|Y]' + 2441 'menu[E|compact][li]' + 2442 'dir[E|compact][li]' + 2443 'pre[E|width|xml:space][#|ZA]' + 2444 'hr[E|align|noshade|size|width][]' + 2445 'blockquote[E|cite][#|Y]' + 2446 'address[E][#|S|p]' + 2447 'center[E][#|Y]' + 2448 'noframes[E][#|Y]' + 2449 'isindex[A|B|prompt][]' + 2450 'fieldset[E][#|legend|Y]' + 2451 'legend[E|accesskey|align][#|S]' + 2452 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2453 'caption[E|align][#|S]' + 2454 'col[ZG][]' + 2455 'colgroup[ZG][col]' + 2456 'thead[ZF][tr]' + 2457 'tr[ZF|bgcolor][th|td]' + 2458 'th[E|ZE][#|Y]' + 2459 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2460 'noscript[E][#|Y]' + 2461 'td[E|ZE][#|Y]' + 2462 'tfoot[ZF][tr]' + 2463 'tbody[ZF][tr]' + 2464 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2465 'base[id|href|target][]' + 2466 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2467 ); 2468 } 2469 2470 return html4; 2471 }; 2472 2473 tinymce.html.Schema = function(settings) { 2474 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2475 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2476 2477 // Creates an lookup table map object for the specified option or the default value 2478 function createLookupTable(option, default_value, extend) { 2479 var value = settings[option]; 2480 2481 if (!value) { 2482 // Get cached default map or make it if needed 2483 value = mapCache[option]; 2484 2485 if (!value) { 2486 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2487 value = tinymce.extend(value, extend); 2488 2489 mapCache[option] = value; 2490 } 2491 } else { 2492 // Create custom map 2493 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2494 } 2495 2496 return value; 2497 }; 2498 2499 settings = settings || {}; 2500 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2501 2502 // Allow all elements and attributes if verify_html is set to false 2503 if (settings.verify_html === false) 2504 settings.valid_elements = '*[*]'; 2505 2506 // Build styles list 2507 if (settings.valid_styles) { 2508 validStyles = {}; 2509 2510 // Convert styles into a rule list 2511 each(settings.valid_styles, function(value, key) { 2512 validStyles[key] = tinymce.explode(value); 2513 }); 2514 } 2515 2516 // Setup map objects 2517 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); 2518 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2519 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2520 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2521 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); 2522 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 2523 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 2524 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); 2525 2526 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2527 function patternToRegExp(str) { 2528 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2529 }; 2530 2531 // Parses the specified valid_elements string and adds to the current rules 2532 // This function is a bit hard to read since it's heavily optimized for speed 2533 function addValidElements(valid_elements) { 2534 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2535 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2536 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2537 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2538 hasPatternsRegExp = /[*?+]/; 2539 2540 if (valid_elements) { 2541 // Split valid elements into an array with rules 2542 valid_elements = split(valid_elements); 2543 2544 if (elements['@']) { 2545 globalAttributes = elements['@'].attributes; 2546 globalAttributesOrder = elements['@'].attributesOrder; 2547 } 2548 2549 // Loop all rules 2550 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2551 // Parse element rule 2552 matches = elementRuleRegExp.exec(valid_elements[ei]); 2553 if (matches) { 2554 // Setup local names for matches 2555 prefix = matches[1]; 2556 elementName = matches[2]; 2557 outputName = matches[3]; 2558 attrData = matches[4]; 2559 2560 // Create new attributes and attributesOrder 2561 attributes = {}; 2562 attributesOrder = []; 2563 2564 // Create the new element 2565 element = { 2566 attributes : attributes, 2567 attributesOrder : attributesOrder 2568 }; 2569 2570 // Padd empty elements prefix 2571 if (prefix === '#') 2572 element.paddEmpty = true; 2573 2574 // Remove empty elements prefix 2575 if (prefix === '-') 2576 element.removeEmpty = true; 2577 2578 // Copy attributes from global rule into current rule 2579 if (globalAttributes) { 2580 for (key in globalAttributes) 2581 attributes[key] = globalAttributes[key]; 2582 2583 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2584 } 2585 2586 // Attributes defined 2587 if (attrData) { 2588 attrData = split(attrData, '|'); 2589 for (ai = 0, al = attrData.length; ai < al; ai++) { 2590 matches = attrRuleRegExp.exec(attrData[ai]); 2591 if (matches) { 2592 attr = {}; 2593 attrType = matches[1]; 2594 attrName = matches[2].replace(/::/g, ':'); 2595 prefix = matches[3]; 2596 value = matches[4]; 2597 2598 // Required 2599 if (attrType === '!') { 2600 element.attributesRequired = element.attributesRequired || []; 2601 element.attributesRequired.push(attrName); 2602 attr.required = true; 2603 } 2604 2605 // Denied from global 2606 if (attrType === '-') { 2607 delete attributes[attrName]; 2608 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2609 continue; 2610 } 2611 2612 // Default value 2613 if (prefix) { 2614 // Default value 2615 if (prefix === '=') { 2616 element.attributesDefault = element.attributesDefault || []; 2617 element.attributesDefault.push({name: attrName, value: value}); 2618 attr.defaultValue = value; 2619 } 2620 2621 // Forced value 2622 if (prefix === ':') { 2623 element.attributesForced = element.attributesForced || []; 2624 element.attributesForced.push({name: attrName, value: value}); 2625 attr.forcedValue = value; 2626 } 2627 2628 // Required values 2629 if (prefix === '<') 2630 attr.validValues = makeMap(value, '?'); 2631 } 2632 2633 // Check for attribute patterns 2634 if (hasPatternsRegExp.test(attrName)) { 2635 element.attributePatterns = element.attributePatterns || []; 2636 attr.pattern = patternToRegExp(attrName); 2637 element.attributePatterns.push(attr); 2638 } else { 2639 // Add attribute to order list if it doesn't already exist 2640 if (!attributes[attrName]) 2641 attributesOrder.push(attrName); 2642 2643 attributes[attrName] = attr; 2644 } 2645 } 2646 } 2647 } 2648 2649 // Global rule, store away these for later usage 2650 if (!globalAttributes && elementName == '@') { 2651 globalAttributes = attributes; 2652 globalAttributesOrder = attributesOrder; 2653 } 2654 2655 // Handle substitute elements such as b/strong 2656 if (outputName) { 2657 element.outputName = elementName; 2658 elements[outputName] = element; 2659 } 2660 2661 // Add pattern or exact element 2662 if (hasPatternsRegExp.test(elementName)) { 2663 element.pattern = patternToRegExp(elementName); 2664 patternElements.push(element); 2665 } else 2666 elements[elementName] = element; 2667 } 2668 } 2669 } 2670 }; 2671 2672 function setValidElements(valid_elements) { 2673 elements = {}; 2674 patternElements = []; 2675 2676 addValidElements(valid_elements); 2677 2678 each(schemaItems, function(element, name) { 2679 children[name] = element.children; 2680 }); 2681 }; 2682 2683 // Adds custom non HTML elements to the schema 2684 function addCustomElements(custom_elements) { 2685 var customElementRegExp = /^(~)?(.+)$/; 2686 2687 if (custom_elements) { 2688 each(split(custom_elements), function(rule) { 2689 var matches = customElementRegExp.exec(rule), 2690 inline = matches[1] === '~', 2691 cloneName = inline ? 'span' : 'div', 2692 name = matches[2]; 2693 2694 children[name] = children[cloneName]; 2695 customElementsMap[name] = cloneName; 2696 2697 // If it's not marked as inline then add it to valid block elements 2698 if (!inline) 2699 blockElementsMap[name] = {}; 2700 2701 // Add custom elements at span/div positions 2702 each(children, function(element, child) { 2703 if (element[cloneName]) 2704 element[name] = element[cloneName]; 2705 }); 2706 }); 2707 } 2708 }; 2709 2710 // Adds valid children to the schema object 2711 function addValidChildren(valid_children) { 2712 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2713 2714 if (valid_children) { 2715 each(split(valid_children), function(rule) { 2716 var matches = childRuleRegExp.exec(rule), parent, prefix; 2717 2718 if (matches) { 2719 prefix = matches[1]; 2720 2721 // Add/remove items from default 2722 if (prefix) 2723 parent = children[matches[2]]; 2724 else 2725 parent = children[matches[2]] = {'#comment' : {}}; 2726 2727 parent = children[matches[2]]; 2728 2729 each(split(matches[3], '|'), function(child) { 2730 if (prefix === '-') 2731 delete parent[child]; 2732 else 2733 parent[child] = {}; 2734 }); 2735 } 2736 }); 2737 } 2738 }; 2739 2740 function getElementRule(name) { 2741 var element = elements[name], i; 2742 2743 // Exact match found 2744 if (element) 2745 return element; 2746 2747 // No exact match then try the patterns 2748 i = patternElements.length; 2749 while (i--) { 2750 element = patternElements[i]; 2751 2752 if (element.pattern.test(name)) 2753 return element; 2754 } 2755 }; 2756 2757 if (!settings.valid_elements) { 2758 // No valid elements defined then clone the elements from the schema spec 2759 each(schemaItems, function(element, name) { 2760 elements[name] = { 2761 attributes : element.attributes, 2762 attributesOrder : element.attributesOrder 2763 }; 2764 2765 children[name] = element.children; 2766 }); 2767 2768 // Switch these on HTML4 2769 if (settings.schema != "html5") { 2770 each(split('strong/b,em/i'), function(item) { 2771 item = split(item, '/'); 2772 elements[item[1]].outputName = item[0]; 2773 }); 2774 } 2775 2776 // Add default alt attribute for images 2777 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 2778 2779 // Remove these if they are empty by default 2780 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 2781 if (elements[name]) { 2782 elements[name].removeEmpty = true; 2783 } 2784 }); 2785 2786 // Padd these by default 2787 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 2788 elements[name].paddEmpty = true; 2789 }); 2790 } else 2791 setValidElements(settings.valid_elements); 2792 2793 addCustomElements(settings.custom_elements); 2794 addValidChildren(settings.valid_children); 2795 addValidElements(settings.extended_valid_elements); 2796 2797 // Todo: Remove this when we fix list handling to be valid 2798 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 2799 2800 // Delete invalid elements 2801 if (settings.invalid_elements) { 2802 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 2803 if (elements[item]) 2804 delete elements[item]; 2805 }); 2806 } 2807 2808 // If the user didn't allow span only allow internal spans 2809 if (!getElementRule('span')) 2810 addValidElements('span[!data-mce-type|*]'); 2811 2812 self.children = children; 2813 2814 self.styles = validStyles; 2815 2816 self.getBoolAttrs = function() { 2817 return boolAttrMap; 2818 }; 2819 2820 self.getBlockElements = function() { 2821 return blockElementsMap; 2822 }; 2823 2824 self.getShortEndedElements = function() { 2825 return shortEndedElementsMap; 2826 }; 2827 2828 self.getSelfClosingElements = function() { 2829 return selfClosingElementsMap; 2830 }; 2831 2832 self.getNonEmptyElements = function() { 2833 return nonEmptyElementsMap; 2834 }; 2835 2836 self.getWhiteSpaceElements = function() { 2837 return whiteSpaceElementsMap; 2838 }; 2839 2840 self.isValidChild = function(name, child) { 2841 var parent = children[name]; 2842 2843 return !!(parent && parent[child]); 2844 }; 2845 2846 self.isValid = function(name, attr) { 2847 var attrPatterns, i, rule = getElementRule(name); 2848 2849 // Check if it's a valid element 2850 if (rule) { 2851 if (attr) { 2852 // Check if attribute name exists 2853 if (rule.attributes[attr]) { 2854 return true; 2855 } 2856 2857 // Check if attribute matches a regexp pattern 2858 attrPatterns = rule.attributePatterns; 2859 if (attrPatterns) { 2860 i = attrPatterns.length; 2861 while (i--) { 2862 if (attrPatterns[i].pattern.test(name)) { 2863 return true; 2864 } 2865 } 2866 } 2867 } else { 2868 return true; 2869 } 2870 } 2871 2872 // No match 2873 return false; 2874 }; 2875 2876 self.getElementRule = getElementRule; 2877 2878 self.getCustomElements = function() { 2879 return customElementsMap; 2880 }; 2881 2882 self.addValidElements = addValidElements; 2883 2884 self.setValidElements = setValidElements; 2885 2886 self.addCustomElements = addCustomElements; 2887 2888 self.addValidChildren = addValidChildren; 2889 }; 2890 })(tinymce); 2891 2892 (function(tinymce) { 2893 tinymce.html.SaxParser = function(settings, schema) { 2894 var self = this, noop = function() {}; 2895 2896 settings = settings || {}; 2897 self.schema = schema = schema || new tinymce.html.Schema(); 2898 2899 if (settings.fix_self_closing !== false) 2900 settings.fix_self_closing = true; 2901 2902 // Add handler functions from settings and setup default handlers 2903 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 2904 if (name) 2905 self[name] = settings[name] || noop; 2906 }); 2907 2908 self.parse = function(html) { 2909 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 2910 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 2911 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 2912 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 2913 2914 function processEndTag(name) { 2915 var pos, i; 2916 2917 // Find position of parent of the same type 2918 pos = stack.length; 2919 while (pos--) { 2920 if (stack[pos].name === name) 2921 break; 2922 } 2923 2924 // Found parent 2925 if (pos >= 0) { 2926 // Close all the open elements 2927 for (i = stack.length - 1; i >= pos; i--) { 2928 name = stack[i]; 2929 2930 if (name.valid) 2931 self.end(name.name); 2932 } 2933 2934 // Remove the open elements from the stack 2935 stack.length = pos; 2936 } 2937 }; 2938 2939 function parseAttribute(match, name, value, val2, val3) { 2940 var attrRule, i; 2941 2942 name = name.toLowerCase(); 2943 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 2944 2945 // Validate name and value 2946 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 2947 attrRule = validAttributesMap[name]; 2948 2949 // Find rule by pattern matching 2950 if (!attrRule && validAttributePatterns) { 2951 i = validAttributePatterns.length; 2952 while (i--) { 2953 attrRule = validAttributePatterns[i]; 2954 if (attrRule.pattern.test(name)) 2955 break; 2956 } 2957 2958 // No rule matched 2959 if (i === -1) 2960 attrRule = null; 2961 } 2962 2963 // No attribute rule found 2964 if (!attrRule) 2965 return; 2966 2967 // Validate value 2968 if (attrRule.validValues && !(value in attrRule.validValues)) 2969 return; 2970 } 2971 2972 // Add attribute to list and map 2973 attrList.map[name] = value; 2974 attrList.push({ 2975 name: name, 2976 value: value 2977 }); 2978 }; 2979 2980 // Precompile RegExps and map objects 2981 tokenRegExp = new RegExp('<(?:' + 2982 '(?:!--([\\w\\W]*?)-->)|' + // Comment 2983 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 2984 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 2985 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 2986 '(?:\\/([^>]+)>)|' + // End element 2987 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 2988 ')', 'g'); 2989 2990 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; 2991 specialElements = { 2992 'script' : /<\/script[^>]*>/gi, 2993 'style' : /<\/style[^>]*>/gi, 2994 'noscript' : /<\/noscript[^>]*>/gi 2995 }; 2996 2997 // Setup lookup tables for empty elements and boolean attributes 2998 shortEndedElements = schema.getShortEndedElements(); 2999 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 3000 fillAttrsMap = schema.getBoolAttrs(); 3001 validate = settings.validate; 3002 removeInternalElements = settings.remove_internals; 3003 fixSelfClosing = settings.fix_self_closing; 3004 isIE = tinymce.isIE; 3005 invalidPrefixRegExp = /^:/; 3006 3007 while (matches = tokenRegExp.exec(html)) { 3008 // Text 3009 if (index < matches.index) 3010 self.text(decode(html.substr(index, matches.index - index))); 3011 3012 if (value = matches[6]) { // End element 3013 value = value.toLowerCase(); 3014 3015 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3016 if (isIE && invalidPrefixRegExp.test(value)) 3017 value = value.substr(1); 3018 3019 processEndTag(value); 3020 } else if (value = matches[7]) { // Start element 3021 value = value.toLowerCase(); 3022 3023 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3024 if (isIE && invalidPrefixRegExp.test(value)) 3025 value = value.substr(1); 3026 3027 isShortEnded = value in shortEndedElements; 3028 3029 // Is self closing tag for example an <li> after an open <li> 3030 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3031 processEndTag(value); 3032 3033 // Validate element 3034 if (!validate || (elementRule = schema.getElementRule(value))) { 3035 isValidElement = true; 3036 3037 // Grab attributes map and patters when validation is enabled 3038 if (validate) { 3039 validAttributesMap = elementRule.attributes; 3040 validAttributePatterns = elementRule.attributePatterns; 3041 } 3042 3043 // Parse attributes 3044 if (attribsValue = matches[8]) { 3045 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3046 3047 // If the element has internal attributes then remove it if we are told to do so 3048 if (isInternalElement && removeInternalElements) 3049 isValidElement = false; 3050 3051 attrList = []; 3052 attrList.map = {}; 3053 3054 attribsValue.replace(attrRegExp, parseAttribute); 3055 } else { 3056 attrList = []; 3057 attrList.map = {}; 3058 } 3059 3060 // Process attributes if validation is enabled 3061 if (validate && !isInternalElement) { 3062 attributesRequired = elementRule.attributesRequired; 3063 attributesDefault = elementRule.attributesDefault; 3064 attributesForced = elementRule.attributesForced; 3065 3066 // Handle forced attributes 3067 if (attributesForced) { 3068 i = attributesForced.length; 3069 while (i--) { 3070 attr = attributesForced[i]; 3071 name = attr.name; 3072 attrValue = attr.value; 3073 3074 if (attrValue === '{$uid}') 3075 attrValue = 'mce_' + idCount++; 3076 3077 attrList.map[name] = attrValue; 3078 attrList.push({name: name, value: attrValue}); 3079 } 3080 } 3081 3082 // Handle default attributes 3083 if (attributesDefault) { 3084 i = attributesDefault.length; 3085 while (i--) { 3086 attr = attributesDefault[i]; 3087 name = attr.name; 3088 3089 if (!(name in attrList.map)) { 3090 attrValue = attr.value; 3091 3092 if (attrValue === '{$uid}') 3093 attrValue = 'mce_' + idCount++; 3094 3095 attrList.map[name] = attrValue; 3096 attrList.push({name: name, value: attrValue}); 3097 } 3098 } 3099 } 3100 3101 // Handle required attributes 3102 if (attributesRequired) { 3103 i = attributesRequired.length; 3104 while (i--) { 3105 if (attributesRequired[i] in attrList.map) 3106 break; 3107 } 3108 3109 // None of the required attributes where found 3110 if (i === -1) 3111 isValidElement = false; 3112 } 3113 3114 // Invalidate element if it's marked as bogus 3115 if (attrList.map['data-mce-bogus']) 3116 isValidElement = false; 3117 } 3118 3119 if (isValidElement) 3120 self.start(value, attrList, isShortEnded); 3121 } else 3122 isValidElement = false; 3123 3124 // Treat script, noscript and style a bit different since they may include code that looks like elements 3125 if (endRegExp = specialElements[value]) { 3126 endRegExp.lastIndex = index = matches.index + matches[0].length; 3127 3128 if (matches = endRegExp.exec(html)) { 3129 if (isValidElement) 3130 text = html.substr(index, matches.index - index); 3131 3132 index = matches.index + matches[0].length; 3133 } else { 3134 text = html.substr(index); 3135 index = html.length; 3136 } 3137 3138 if (isValidElement && text.length > 0) 3139 self.text(text, true); 3140 3141 if (isValidElement) 3142 self.end(value); 3143 3144 tokenRegExp.lastIndex = index; 3145 continue; 3146 } 3147 3148 // Push value on to stack 3149 if (!isShortEnded) { 3150 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3151 stack.push({name: value, valid: isValidElement}); 3152 else if (isValidElement) 3153 self.end(value); 3154 } 3155 } else if (value = matches[1]) { // Comment 3156 self.comment(value); 3157 } else if (value = matches[2]) { // CDATA 3158 self.cdata(value); 3159 } else if (value = matches[3]) { // DOCTYPE 3160 self.doctype(value); 3161 } else if (value = matches[4]) { // PI 3162 self.pi(value, matches[5]); 3163 } 3164 3165 index = matches.index + matches[0].length; 3166 } 3167 3168 // Text 3169 if (index < html.length) 3170 self.text(decode(html.substr(index))); 3171 3172 // Close any open elements 3173 for (i = stack.length - 1; i >= 0; i--) { 3174 value = stack[i]; 3175 3176 if (value.valid) 3177 self.end(value.name); 3178 } 3179 }; 3180 } 3181 })(tinymce); 3182 3183 (function(tinymce) { 3184 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3185 '#text' : 3, 3186 '#comment' : 8, 3187 '#cdata' : 4, 3188 '#pi' : 7, 3189 '#doctype' : 10, 3190 '#document-fragment' : 11 3191 }; 3192 3193 // Walks the tree left/right 3194 function walk(node, root_node, prev) { 3195 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3196 3197 // Walk into nodes if it has a start 3198 if (node[startName]) 3199 return node[startName]; 3200 3201 // Return the sibling if it has one 3202 if (node !== root_node) { 3203 sibling = node[siblingName]; 3204 3205 if (sibling) 3206 return sibling; 3207 3208 // Walk up the parents to look for siblings 3209 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3210 sibling = parent[siblingName]; 3211 3212 if (sibling) 3213 return sibling; 3214 } 3215 } 3216 }; 3217 3218 function Node(name, type) { 3219 this.name = name; 3220 this.type = type; 3221 3222 if (type === 1) { 3223 this.attributes = []; 3224 this.attributes.map = {}; 3225 } 3226 } 3227 3228 tinymce.extend(Node.prototype, { 3229 replace : function(node) { 3230 var self = this; 3231 3232 if (node.parent) 3233 node.remove(); 3234 3235 self.insert(node, self); 3236 self.remove(); 3237 3238 return self; 3239 }, 3240 3241 attr : function(name, value) { 3242 var self = this, attrs, i, undef; 3243 3244 if (typeof name !== "string") { 3245 for (i in name) 3246 self.attr(i, name[i]); 3247 3248 return self; 3249 } 3250 3251 if (attrs = self.attributes) { 3252 if (value !== undef) { 3253 // Remove attribute 3254 if (value === null) { 3255 if (name in attrs.map) { 3256 delete attrs.map[name]; 3257 3258 i = attrs.length; 3259 while (i--) { 3260 if (attrs[i].name === name) { 3261 attrs = attrs.splice(i, 1); 3262 return self; 3263 } 3264 } 3265 } 3266 3267 return self; 3268 } 3269 3270 // Set attribute 3271 if (name in attrs.map) { 3272 // Set attribute 3273 i = attrs.length; 3274 while (i--) { 3275 if (attrs[i].name === name) { 3276 attrs[i].value = value; 3277 break; 3278 } 3279 } 3280 } else 3281 attrs.push({name: name, value: value}); 3282 3283 attrs.map[name] = value; 3284 3285 return self; 3286 } else { 3287 return attrs.map[name]; 3288 } 3289 } 3290 }, 3291 3292 clone : function() { 3293 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3294 3295 // Clone element attributes 3296 if (selfAttrs = self.attributes) { 3297 cloneAttrs = []; 3298 cloneAttrs.map = {}; 3299 3300 for (i = 0, l = selfAttrs.length; i < l; i++) { 3301 selfAttr = selfAttrs[i]; 3302 3303 // Clone everything except id 3304 if (selfAttr.name !== 'id') { 3305 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3306 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3307 } 3308 } 3309 3310 clone.attributes = cloneAttrs; 3311 } 3312 3313 clone.value = self.value; 3314 clone.shortEnded = self.shortEnded; 3315 3316 return clone; 3317 }, 3318 3319 wrap : function(wrapper) { 3320 var self = this; 3321 3322 self.parent.insert(wrapper, self); 3323 wrapper.append(self); 3324 3325 return self; 3326 }, 3327 3328 unwrap : function() { 3329 var self = this, node, next; 3330 3331 for (node = self.firstChild; node; ) { 3332 next = node.next; 3333 self.insert(node, self, true); 3334 node = next; 3335 } 3336 3337 self.remove(); 3338 }, 3339 3340 remove : function() { 3341 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3342 3343 if (parent) { 3344 if (parent.firstChild === self) { 3345 parent.firstChild = next; 3346 3347 if (next) 3348 next.prev = null; 3349 } else { 3350 prev.next = next; 3351 } 3352 3353 if (parent.lastChild === self) { 3354 parent.lastChild = prev; 3355 3356 if (prev) 3357 prev.next = null; 3358 } else { 3359 next.prev = prev; 3360 } 3361 3362 self.parent = self.next = self.prev = null; 3363 } 3364 3365 return self; 3366 }, 3367 3368 append : function(node) { 3369 var self = this, last; 3370 3371 if (node.parent) 3372 node.remove(); 3373 3374 last = self.lastChild; 3375 if (last) { 3376 last.next = node; 3377 node.prev = last; 3378 self.lastChild = node; 3379 } else 3380 self.lastChild = self.firstChild = node; 3381 3382 node.parent = self; 3383 3384 return node; 3385 }, 3386 3387 insert : function(node, ref_node, before) { 3388 var parent; 3389 3390 if (node.parent) 3391 node.remove(); 3392 3393 parent = ref_node.parent || this; 3394 3395 if (before) { 3396 if (ref_node === parent.firstChild) 3397 parent.firstChild = node; 3398 else 3399 ref_node.prev.next = node; 3400 3401 node.prev = ref_node.prev; 3402 node.next = ref_node; 3403 ref_node.prev = node; 3404 } else { 3405 if (ref_node === parent.lastChild) 3406 parent.lastChild = node; 3407 else 3408 ref_node.next.prev = node; 3409 3410 node.next = ref_node.next; 3411 node.prev = ref_node; 3412 ref_node.next = node; 3413 } 3414 3415 node.parent = parent; 3416 3417 return node; 3418 }, 3419 3420 getAll : function(name) { 3421 var self = this, node, collection = []; 3422 3423 for (node = self.firstChild; node; node = walk(node, self)) { 3424 if (node.name === name) 3425 collection.push(node); 3426 } 3427 3428 return collection; 3429 }, 3430 3431 empty : function() { 3432 var self = this, nodes, i, node; 3433 3434 // Remove all children 3435 if (self.firstChild) { 3436 nodes = []; 3437 3438 // Collect the children 3439 for (node = self.firstChild; node; node = walk(node, self)) 3440 nodes.push(node); 3441 3442 // Remove the children 3443 i = nodes.length; 3444 while (i--) { 3445 node = nodes[i]; 3446 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3447 } 3448 } 3449 3450 self.firstChild = self.lastChild = null; 3451 3452 return self; 3453 }, 3454 3455 isEmpty : function(elements) { 3456 var self = this, node = self.firstChild, i, name; 3457 3458 if (node) { 3459 do { 3460 if (node.type === 1) { 3461 // Ignore bogus elements 3462 if (node.attributes.map['data-mce-bogus']) 3463 continue; 3464 3465 // Keep empty elements like <img /> 3466 if (elements[node.name]) 3467 return false; 3468 3469 // Keep elements with data attributes or name attribute like <a name="1"></a> 3470 i = node.attributes.length; 3471 while (i--) { 3472 name = node.attributes[i].name; 3473 if (name === "name" || name.indexOf('data-') === 0) 3474 return false; 3475 } 3476 } 3477 3478 // Keep comments 3479 if (node.type === 8) 3480 return false; 3481 3482 // Keep non whitespace text nodes 3483 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3484 return false; 3485 } while (node = walk(node, self)); 3486 } 3487 3488 return true; 3489 }, 3490 3491 walk : function(prev) { 3492 return walk(this, null, prev); 3493 } 3494 }); 3495 3496 tinymce.extend(Node, { 3497 create : function(name, attrs) { 3498 var node, attrName; 3499 3500 // Create node 3501 node = new Node(name, typeLookup[name] || 1); 3502 3503 // Add attributes if needed 3504 if (attrs) { 3505 for (attrName in attrs) 3506 node.attr(attrName, attrs[attrName]); 3507 } 3508 3509 return node; 3510 } 3511 }); 3512 3513 tinymce.html.Node = Node; 3514 })(tinymce); 3515 3516 (function(tinymce) { 3517 var Node = tinymce.html.Node; 3518 3519 tinymce.html.DomParser = function(settings, schema) { 3520 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3521 3522 settings = settings || {}; 3523 settings.validate = "validate" in settings ? settings.validate : true; 3524 settings.root_name = settings.root_name || 'body'; 3525 self.schema = schema = schema || new tinymce.html.Schema(); 3526 3527 function fixInvalidChildren(nodes) { 3528 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3529 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; 3530 3531 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3532 nonEmptyElements = schema.getNonEmptyElements(); 3533 3534 for (ni = 0; ni < nodes.length; ni++) { 3535 node = nodes[ni]; 3536 3537 // Already removed 3538 if (!node.parent) 3539 continue; 3540 3541 // Get list of all parent nodes until we find a valid parent to stick the child into 3542 parents = [node]; 3543 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3544 parents.push(parent); 3545 3546 // Found a suitable parent 3547 if (parent && parents.length > 1) { 3548 // Reverse the array since it makes looping easier 3549 parents.reverse(); 3550 3551 // Clone the related parent and insert that after the moved node 3552 newParent = currentNode = self.filterNode(parents[0].clone()); 3553 3554 // Start cloning and moving children on the left side of the target node 3555 for (i = 0; i < parents.length - 1; i++) { 3556 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3557 tempNode = self.filterNode(parents[i].clone()); 3558 currentNode.append(tempNode); 3559 } else 3560 tempNode = currentNode; 3561 3562 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3563 nextNode = childNode.next; 3564 tempNode.append(childNode); 3565 childNode = nextNode; 3566 } 3567 3568 currentNode = tempNode; 3569 } 3570 3571 if (!newParent.isEmpty(nonEmptyElements)) { 3572 parent.insert(newParent, parents[0], true); 3573 parent.insert(node, newParent); 3574 } else { 3575 parent.insert(node, parents[0], true); 3576 } 3577 3578 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3579 parent = parents[0]; 3580 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3581 parent.empty().remove(); 3582 } 3583 } else if (node.parent) { 3584 // If it's an LI try to find a UL/OL for it or wrap it 3585 if (node.name === 'li') { 3586 sibling = node.prev; 3587 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3588 sibling.append(node); 3589 continue; 3590 } 3591 3592 sibling = node.next; 3593 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3594 sibling.insert(node, sibling.firstChild, true); 3595 continue; 3596 } 3597 3598 node.wrap(self.filterNode(new Node('ul', 1))); 3599 continue; 3600 } 3601 3602 // Try wrapping the element in a DIV 3603 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3604 node.wrap(self.filterNode(new Node('div', 1))); 3605 } else { 3606 // We failed wrapping it, then remove or unwrap it 3607 if (node.name === 'style' || node.name === 'script') 3608 node.empty().remove(); 3609 else 3610 node.unwrap(); 3611 } 3612 } 3613 } 3614 }; 3615 3616 self.filterNode = function(node) { 3617 var i, name, list; 3618 3619 // Run element filters 3620 if (name in nodeFilters) { 3621 list = matchedNodes[name]; 3622 3623 if (list) 3624 list.push(node); 3625 else 3626 matchedNodes[name] = [node]; 3627 } 3628 3629 // Run attribute filters 3630 i = attributeFilters.length; 3631 while (i--) { 3632 name = attributeFilters[i].name; 3633 3634 if (name in node.attributes.map) { 3635 list = matchedAttributes[name]; 3636 3637 if (list) 3638 list.push(node); 3639 else 3640 matchedAttributes[name] = [node]; 3641 } 3642 } 3643 3644 return node; 3645 }; 3646 3647 self.addNodeFilter = function(name, callback) { 3648 tinymce.each(tinymce.explode(name), function(name) { 3649 var list = nodeFilters[name]; 3650 3651 if (!list) 3652 nodeFilters[name] = list = []; 3653 3654 list.push(callback); 3655 }); 3656 }; 3657 3658 self.addAttributeFilter = function(name, callback) { 3659 tinymce.each(tinymce.explode(name), function(name) { 3660 var i; 3661 3662 for (i = 0; i < attributeFilters.length; i++) { 3663 if (attributeFilters[i].name === name) { 3664 attributeFilters[i].callbacks.push(callback); 3665 return; 3666 } 3667 } 3668 3669 attributeFilters.push({name: name, callbacks: [callback]}); 3670 }); 3671 }; 3672 3673 self.parse = function(html, args) { 3674 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3675 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3676 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3677 3678 args = args || {}; 3679 matchedNodes = {}; 3680 matchedAttributes = {}; 3681 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3682 nonEmptyElements = schema.getNonEmptyElements(); 3683 children = schema.children; 3684 validate = settings.validate; 3685 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3686 3687 whiteSpaceElements = schema.getWhiteSpaceElements(); 3688 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3689 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3690 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3691 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3692 3693 function addRootBlocks() { 3694 var node = rootNode.firstChild, next, rootBlockNode; 3695 3696 while (node) { 3697 next = node.next; 3698 3699 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3700 if (!rootBlockNode) { 3701 // Create a new root block element 3702 rootBlockNode = createNode(rootBlockName, 1); 3703 rootNode.insert(rootBlockNode, node); 3704 rootBlockNode.append(node); 3705 } else 3706 rootBlockNode.append(node); 3707 } else { 3708 rootBlockNode = null; 3709 } 3710 3711 node = next; 3712 }; 3713 }; 3714 3715 function createNode(name, type) { 3716 var node = new Node(name, type), list; 3717 3718 if (name in nodeFilters) { 3719 list = matchedNodes[name]; 3720 3721 if (list) 3722 list.push(node); 3723 else 3724 matchedNodes[name] = [node]; 3725 } 3726 3727 return node; 3728 }; 3729 3730 function removeWhitespaceBefore(node) { 3731 var textNode, textVal, sibling; 3732 3733 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3734 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3735 3736 if (textVal.length > 0) { 3737 textNode.value = textVal; 3738 textNode = textNode.prev; 3739 } else { 3740 sibling = textNode.prev; 3741 textNode.remove(); 3742 textNode = sibling; 3743 } 3744 } 3745 }; 3746 3747 function cloneAndExcludeBlocks(input) { 3748 var name, output = {}; 3749 3750 for (name in input) { 3751 if (name !== 'li' && name != 'p') { 3752 output[name] = input[name]; 3753 } 3754 } 3755 3756 return output; 3757 }; 3758 3759 parser = new tinymce.html.SaxParser({ 3760 validate : validate, 3761 3762 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 3763 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 3764 3765 cdata: function(text) { 3766 node.append(createNode('#cdata', 4)).value = text; 3767 }, 3768 3769 text: function(text, raw) { 3770 var textNode; 3771 3772 // Trim all redundant whitespace on non white space elements 3773 if (!isInWhiteSpacePreservedElement) { 3774 text = text.replace(allWhiteSpaceRegExp, ' '); 3775 3776 if (node.lastChild && blockElements[node.lastChild.name]) 3777 text = text.replace(startWhiteSpaceRegExp, ''); 3778 } 3779 3780 // Do we need to create the node 3781 if (text.length !== 0) { 3782 textNode = createNode('#text', 3); 3783 textNode.raw = !!raw; 3784 node.append(textNode).value = text; 3785 } 3786 }, 3787 3788 comment: function(text) { 3789 node.append(createNode('#comment', 8)).value = text; 3790 }, 3791 3792 pi: function(name, text) { 3793 node.append(createNode(name, 7)).value = text; 3794 removeWhitespaceBefore(node); 3795 }, 3796 3797 doctype: function(text) { 3798 var newNode; 3799 3800 newNode = node.append(createNode('#doctype', 10)); 3801 newNode.value = text; 3802 removeWhitespaceBefore(node); 3803 }, 3804 3805 start: function(name, attrs, empty) { 3806 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 3807 3808 elementRule = validate ? schema.getElementRule(name) : {}; 3809 if (elementRule) { 3810 newNode = createNode(elementRule.outputName || name, 1); 3811 newNode.attributes = attrs; 3812 newNode.shortEnded = empty; 3813 3814 node.append(newNode); 3815 3816 // Check if node is valid child of the parent node is the child is 3817 // unknown we don't collect it since it's probably a custom element 3818 parent = children[node.name]; 3819 if (parent && children[newNode.name] && !parent[newNode.name]) 3820 invalidChildren.push(newNode); 3821 3822 attrFiltersLen = attributeFilters.length; 3823 while (attrFiltersLen--) { 3824 attrName = attributeFilters[attrFiltersLen].name; 3825 3826 if (attrName in attrs.map) { 3827 list = matchedAttributes[attrName]; 3828 3829 if (list) 3830 list.push(newNode); 3831 else 3832 matchedAttributes[attrName] = [newNode]; 3833 } 3834 } 3835 3836 // Trim whitespace before block 3837 if (blockElements[name]) 3838 removeWhitespaceBefore(newNode); 3839 3840 // Change current node if the element wasn't empty i.e not <br /> or <img /> 3841 if (!empty) 3842 node = newNode; 3843 3844 // Check if we are inside a whitespace preserved element 3845 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3846 isInWhiteSpacePreservedElement = true; 3847 } 3848 } 3849 }, 3850 3851 end: function(name) { 3852 var textNode, elementRule, text, sibling, tempNode; 3853 3854 elementRule = validate ? schema.getElementRule(name) : {}; 3855 if (elementRule) { 3856 if (blockElements[name]) { 3857 if (!isInWhiteSpacePreservedElement) { 3858 // Trim whitespace of the first node in a block 3859 textNode = node.firstChild; 3860 if (textNode && textNode.type === 3) { 3861 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3862 3863 // Any characters left after trim or should we remove it 3864 if (text.length > 0) { 3865 textNode.value = text; 3866 textNode = textNode.next; 3867 } else { 3868 sibling = textNode.next; 3869 textNode.remove(); 3870 textNode = sibling; 3871 } 3872 3873 // Remove any pure whitespace siblings 3874 while (textNode && textNode.type === 3) { 3875 text = textNode.value; 3876 sibling = textNode.next; 3877 3878 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3879 textNode.remove(); 3880 textNode = sibling; 3881 } 3882 3883 textNode = sibling; 3884 } 3885 } 3886 3887 // Trim whitespace of the last node in a block 3888 textNode = node.lastChild; 3889 if (textNode && textNode.type === 3) { 3890 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 3891 3892 // Any characters left after trim or should we remove it 3893 if (text.length > 0) { 3894 textNode.value = text; 3895 textNode = textNode.prev; 3896 } else { 3897 sibling = textNode.prev; 3898 textNode.remove(); 3899 textNode = sibling; 3900 } 3901 3902 // Remove any pure whitespace siblings 3903 while (textNode && textNode.type === 3) { 3904 text = textNode.value; 3905 sibling = textNode.prev; 3906 3907 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3908 textNode.remove(); 3909 textNode = sibling; 3910 } 3911 3912 textNode = sibling; 3913 } 3914 } 3915 } 3916 3917 // Trim start white space 3918 textNode = node.prev; 3919 if (textNode && textNode.type === 3) { 3920 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3921 3922 if (text.length > 0) 3923 textNode.value = text; 3924 else 3925 textNode.remove(); 3926 } 3927 } 3928 3929 // Check if we exited a whitespace preserved element 3930 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3931 isInWhiteSpacePreservedElement = false; 3932 } 3933 3934 // Handle empty nodes 3935 if (elementRule.removeEmpty || elementRule.paddEmpty) { 3936 if (node.isEmpty(nonEmptyElements)) { 3937 if (elementRule.paddEmpty) 3938 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 3939 else { 3940 // Leave nodes that have a name like <a name="name"> 3941 if (!node.attributes.map.name && !node.attributes.map.id) { 3942 tempNode = node.parent; 3943 node.empty().remove(); 3944 node = tempNode; 3945 return; 3946 } 3947 } 3948 } 3949 } 3950 3951 node = node.parent; 3952 } 3953 } 3954 }, schema); 3955 3956 rootNode = node = new Node(args.context || settings.root_name, 11); 3957 3958 parser.parse(html); 3959 3960 // Fix invalid children or report invalid children in a contextual parsing 3961 if (validate && invalidChildren.length) { 3962 if (!args.context) 3963 fixInvalidChildren(invalidChildren); 3964 else 3965 args.invalid = true; 3966 } 3967 3968 // Wrap nodes in the root into block elements if the root is body 3969 if (rootBlockName && rootNode.name == 'body') 3970 addRootBlocks(); 3971 3972 // Run filters only when the contents is valid 3973 if (!args.invalid) { 3974 // Run node filters 3975 for (name in matchedNodes) { 3976 list = nodeFilters[name]; 3977 nodes = matchedNodes[name]; 3978 3979 // Remove already removed children 3980 fi = nodes.length; 3981 while (fi--) { 3982 if (!nodes[fi].parent) 3983 nodes.splice(fi, 1); 3984 } 3985 3986 for (i = 0, l = list.length; i < l; i++) 3987 list[i](nodes, name, args); 3988 } 3989 3990 // Run attribute filters 3991 for (i = 0, l = attributeFilters.length; i < l; i++) { 3992 list = attributeFilters[i]; 3993 3994 if (list.name in matchedAttributes) { 3995 nodes = matchedAttributes[list.name]; 3996 3997 // Remove already removed children 3998 fi = nodes.length; 3999 while (fi--) { 4000 if (!nodes[fi].parent) 4001 nodes.splice(fi, 1); 4002 } 4003 4004 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 4005 list.callbacks[fi](nodes, list.name, args); 4006 } 4007 } 4008 } 4009 4010 return rootNode; 4011 }; 4012 4013 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4014 // make it possible to place the caret inside empty blocks. This logic tries to remove 4015 // these elements and keep br elements that where intended to be there intact 4016 if (settings.remove_trailing_brs) { 4017 self.addNodeFilter('br', function(nodes, name) { 4018 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4019 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4020 4021 // Remove brs from body element as well 4022 blockElements.body = 1; 4023 4024 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4025 for (i = 0; i < l; i++) { 4026 node = nodes[i]; 4027 parent = node.parent; 4028 4029 if (blockElements[node.parent.name] && node === parent.lastChild) { 4030 // Loop all nodes to the left of the current node and check for other BR elements 4031 // excluding bookmarks since they are invisible 4032 prev = node.prev; 4033 while (prev) { 4034 prevName = prev.name; 4035 4036 // Ignore bookmarks 4037 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4038 // Found a non BR element 4039 if (prevName !== "br") 4040 break; 4041 4042 // Found another br it's a <br><br> structure then don't remove anything 4043 if (prevName === 'br') { 4044 node = null; 4045 break; 4046 } 4047 } 4048 4049 prev = prev.prev; 4050 } 4051 4052 if (node) { 4053 node.remove(); 4054 4055 // Is the parent to be considered empty after we removed the BR 4056 if (parent.isEmpty(nonEmptyElements)) { 4057 elementRule = schema.getElementRule(parent.name); 4058 4059 // Remove or padd the element depending on schema rule 4060 if (elementRule) { 4061 if (elementRule.removeEmpty) 4062 parent.remove(); 4063 else if (elementRule.paddEmpty) 4064 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4065 } 4066 } 4067 } 4068 } else { 4069 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4070 lastParent = node; 4071 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4072 lastParent = parent; 4073 4074 if (blockElements[parent.name]) { 4075 break; 4076 } 4077 4078 parent = parent.parent; 4079 } 4080 4081 if (lastParent === parent) { 4082 textNode = new tinymce.html.Node('#text', 3); 4083 textNode.value = '\u00a0'; 4084 node.replace(textNode); 4085 } 4086 } 4087 } 4088 }); 4089 } 4090 4091 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4092 if (!settings.allow_html_in_named_anchor) { 4093 self.addAttributeFilter('id,name', function(nodes, name) { 4094 var i = nodes.length, sibling, prevSibling, parent, node; 4095 4096 while (i--) { 4097 node = nodes[i]; 4098 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4099 parent = node.parent; 4100 4101 // Move children after current node 4102 sibling = node.lastChild; 4103 do { 4104 prevSibling = sibling.prev; 4105 parent.insert(sibling, node); 4106 sibling = prevSibling; 4107 } while (sibling); 4108 } 4109 } 4110 }); 4111 } 4112 } 4113 })(tinymce); 4114 4115 tinymce.html.Writer = function(settings) { 4116 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4117 4118 settings = settings || {}; 4119 indent = settings.indent; 4120 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4121 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4122 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4123 htmlOutput = settings.element_format == "html"; 4124 4125 return { 4126 start: function(name, attrs, empty) { 4127 var i, l, attr, value; 4128 4129 if (indent && indentBefore[name] && html.length > 0) { 4130 value = html[html.length - 1]; 4131 4132 if (value.length > 0 && value !== '\n') 4133 html.push('\n'); 4134 } 4135 4136 html.push('<', name); 4137 4138 if (attrs) { 4139 for (i = 0, l = attrs.length; i < l; i++) { 4140 attr = attrs[i]; 4141 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4142 } 4143 } 4144 4145 if (!empty || htmlOutput) 4146 html[html.length] = '>'; 4147 else 4148 html[html.length] = ' />'; 4149 4150 if (empty && indent && indentAfter[name] && html.length > 0) { 4151 value = html[html.length - 1]; 4152 4153 if (value.length > 0 && value !== '\n') 4154 html.push('\n'); 4155 } 4156 }, 4157 4158 end: function(name) { 4159 var value; 4160 4161 /*if (indent && indentBefore[name] && html.length > 0) { 4162 value = html[html.length - 1]; 4163 4164 if (value.length > 0 && value !== '\n') 4165 html.push('\n'); 4166 }*/ 4167 4168 html.push('</', name, '>'); 4169 4170 if (indent && indentAfter[name] && html.length > 0) { 4171 value = html[html.length - 1]; 4172 4173 if (value.length > 0 && value !== '\n') 4174 html.push('\n'); 4175 } 4176 }, 4177 4178 text: function(text, raw) { 4179 if (text.length > 0) 4180 html[html.length] = raw ? text : encode(text); 4181 }, 4182 4183 cdata: function(text) { 4184 html.push('<![CDATA[', text, ']]>'); 4185 }, 4186 4187 comment: function(text) { 4188 html.push('<!--', text, '-->'); 4189 }, 4190 4191 pi: function(name, text) { 4192 if (text) 4193 html.push('<?', name, ' ', text, '?>'); 4194 else 4195 html.push('<?', name, '?>'); 4196 4197 if (indent) 4198 html.push('\n'); 4199 }, 4200 4201 doctype: function(text) { 4202 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4203 }, 4204 4205 reset: function() { 4206 html.length = 0; 4207 }, 4208 4209 getContent: function() { 4210 return html.join('').replace(/\n$/, ''); 4211 } 4212 }; 4213 }; 4214 4215 (function(tinymce) { 4216 tinymce.html.Serializer = function(settings, schema) { 4217 var self = this, writer = new tinymce.html.Writer(settings); 4218 4219 settings = settings || {}; 4220 settings.validate = "validate" in settings ? settings.validate : true; 4221 4222 self.schema = schema = schema || new tinymce.html.Schema(); 4223 self.writer = writer; 4224 4225 self.serialize = function(node) { 4226 var handlers, validate; 4227 4228 validate = settings.validate; 4229 4230 handlers = { 4231 // #text 4232 3: function(node, raw) { 4233 writer.text(node.value, node.raw); 4234 }, 4235 4236 // #comment 4237 8: function(node) { 4238 writer.comment(node.value); 4239 }, 4240 4241 // Processing instruction 4242 7: function(node) { 4243 writer.pi(node.name, node.value); 4244 }, 4245 4246 // Doctype 4247 10: function(node) { 4248 writer.doctype(node.value); 4249 }, 4250 4251 // CDATA 4252 4: function(node) { 4253 writer.cdata(node.value); 4254 }, 4255 4256 // Document fragment 4257 11: function(node) { 4258 if ((node = node.firstChild)) { 4259 do { 4260 walk(node); 4261 } while (node = node.next); 4262 } 4263 } 4264 }; 4265 4266 writer.reset(); 4267 4268 function walk(node) { 4269 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4270 4271 if (!handler) { 4272 name = node.name; 4273 isEmpty = node.shortEnded; 4274 attrs = node.attributes; 4275 4276 // Sort attributes 4277 if (validate && attrs && attrs.length > 1) { 4278 sortedAttrs = []; 4279 sortedAttrs.map = {}; 4280 4281 elementRule = schema.getElementRule(node.name); 4282 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4283 attrName = elementRule.attributesOrder[i]; 4284 4285 if (attrName in attrs.map) { 4286 attrValue = attrs.map[attrName]; 4287 sortedAttrs.map[attrName] = attrValue; 4288 sortedAttrs.push({name: attrName, value: attrValue}); 4289 } 4290 } 4291 4292 for (i = 0, l = attrs.length; i < l; i++) { 4293 attrName = attrs[i].name; 4294 4295 if (!(attrName in sortedAttrs.map)) { 4296 attrValue = attrs.map[attrName]; 4297 sortedAttrs.map[attrName] = attrValue; 4298 sortedAttrs.push({name: attrName, value: attrValue}); 4299 } 4300 } 4301 4302 attrs = sortedAttrs; 4303 } 4304 4305 writer.start(node.name, attrs, isEmpty); 4306 4307 if (!isEmpty) { 4308 if ((node = node.firstChild)) { 4309 do { 4310 walk(node); 4311 } while (node = node.next); 4312 } 4313 4314 writer.end(name); 4315 } 4316 } else 4317 handler(node); 4318 } 4319 4320 // Serialize element and treat all non elements as fragments 4321 if (node.type == 1 && !settings.inner) 4322 walk(node); 4323 else 4324 handlers[11](node); 4325 4326 return writer.getContent(); 4327 }; 4328 } 4329 })(tinymce); 4330 4331 // JSLint defined globals 4332 /*global tinymce:false, window:false */ 4333 4334 tinymce.dom = {}; 4335 4336 (function(namespace, expando) { 4337 var w3cEventModel = !!document.addEventListener; 4338 4339 function addEvent(target, name, callback, capture) { 4340 if (target.addEventListener) { 4341 target.addEventListener(name, callback, capture || false); 4342 } else if (target.attachEvent) { 4343 target.attachEvent('on' + name, callback); 4344 } 4345 } 4346 4347 function removeEvent(target, name, callback, capture) { 4348 if (target.removeEventListener) { 4349 target.removeEventListener(name, callback, capture || false); 4350 } else if (target.detachEvent) { 4351 target.detachEvent('on' + name, callback); 4352 } 4353 } 4354 4355 function fix(original_event, data) { 4356 var name, event = data || {}; 4357 4358 // Dummy function that gets replaced on the delegation state functions 4359 function returnFalse() { 4360 return false; 4361 } 4362 4363 // Dummy function that gets replaced on the delegation state functions 4364 function returnTrue() { 4365 return true; 4366 } 4367 4368 // Copy all properties from the original event 4369 for (name in original_event) { 4370 // layerX/layerY is deprecated in Chrome and produces a warning 4371 if (name !== "layerX" && name !== "layerY") { 4372 event[name] = original_event[name]; 4373 } 4374 } 4375 4376 // Normalize target IE uses srcElement 4377 if (!event.target) { 4378 event.target = event.srcElement || document; 4379 } 4380 4381 // Add preventDefault method 4382 event.preventDefault = function() { 4383 event.isDefaultPrevented = returnTrue; 4384 4385 // Execute preventDefault on the original event object 4386 if (original_event) { 4387 if (original_event.preventDefault) { 4388 original_event.preventDefault(); 4389 } else { 4390 original_event.returnValue = false; // IE 4391 } 4392 } 4393 }; 4394 4395 // Add stopPropagation 4396 event.stopPropagation = function() { 4397 event.isPropagationStopped = returnTrue; 4398 4399 // Execute stopPropagation on the original event object 4400 if (original_event) { 4401 if (original_event.stopPropagation) { 4402 original_event.stopPropagation(); 4403 } else { 4404 original_event.cancelBubble = true; // IE 4405 } 4406 } 4407 }; 4408 4409 // Add stopImmediatePropagation 4410 event.stopImmediatePropagation = function() { 4411 event.isImmediatePropagationStopped = returnTrue; 4412 event.stopPropagation(); 4413 }; 4414 4415 // Add event delegation states 4416 if (!event.isDefaultPrevented) { 4417 event.isDefaultPrevented = returnFalse; 4418 event.isPropagationStopped = returnFalse; 4419 event.isImmediatePropagationStopped = returnFalse; 4420 } 4421 4422 return event; 4423 } 4424 4425 function bindOnReady(win, callback, event_utils) { 4426 var doc = win.document, event = {type: 'ready'}; 4427 4428 // Gets called when the DOM is ready 4429 function readyHandler() { 4430 if (!event_utils.domLoaded) { 4431 event_utils.domLoaded = true; 4432 callback(event); 4433 } 4434 } 4435 4436 // Use W3C method 4437 if (w3cEventModel) { 4438 addEvent(win, 'DOMContentLoaded', readyHandler); 4439 } else { 4440 // Use IE method 4441 addEvent(doc, "readystatechange", function() { 4442 if (doc.readyState === "complete") { 4443 removeEvent(doc, "readystatechange", arguments.callee); 4444 readyHandler(); 4445 } 4446 }); 4447 4448 // Wait until we can scroll, when we can the DOM is initialized 4449 if (doc.documentElement.doScroll && win === win.top) { 4450 (function() { 4451 try { 4452 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4453 // http://javascript.nwbox.com/IEContentLoaded/ 4454 doc.documentElement.doScroll("left"); 4455 } catch (ex) { 4456 setTimeout(arguments.callee, 0); 4457 return; 4458 } 4459 4460 readyHandler(); 4461 })(); 4462 } 4463 } 4464 4465 // Fallback if any of the above methods should fail for some odd reason 4466 addEvent(win, 'load', readyHandler); 4467 } 4468 4469 function EventUtils(proxy) { 4470 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4471 4472 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4473 hasFocusIn = "onfocusin" in document.documentElement; 4474 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4475 count = 1; 4476 4477 // State if the DOMContentLoaded was executed or not 4478 self.domLoaded = false; 4479 self.events = events; 4480 4481 function executeHandlers(evt, id) { 4482 var callbackList, i, l, callback; 4483 4484 callbackList = events[id][evt.type]; 4485 if (callbackList) { 4486 for (i = 0, l = callbackList.length; i < l; i++) { 4487 callback = callbackList[i]; 4488 4489 // Check if callback exists might be removed if a unbind is called inside the callback 4490 if (callback && callback.func.call(callback.scope, evt) === false) { 4491 evt.preventDefault(); 4492 } 4493 4494 // Should we stop propagation to immediate listeners 4495 if (evt.isImmediatePropagationStopped()) { 4496 return; 4497 } 4498 } 4499 } 4500 } 4501 4502 self.bind = function(target, names, callback, scope) { 4503 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4504 4505 // Native event handler function patches the event and executes the callbacks for the expando 4506 function defaultNativeHandler(evt) { 4507 executeHandlers(fix(evt || win.event), id); 4508 } 4509 4510 // Don't bind to text nodes or comments 4511 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4512 return; 4513 } 4514 4515 // Create or get events id for the target 4516 if (!target[expando]) { 4517 id = count++; 4518 target[expando] = id; 4519 events[id] = {}; 4520 } else { 4521 id = target[expando]; 4522 4523 if (!events[id]) { 4524 events[id] = {}; 4525 } 4526 } 4527 4528 // Setup the specified scope or use the target as a default 4529 scope = scope || target; 4530 4531 // Split names and bind each event, enables you to bind multiple events with one call 4532 names = names.split(' '); 4533 i = names.length; 4534 while (i--) { 4535 name = names[i]; 4536 nativeHandler = defaultNativeHandler; 4537 fakeName = capture = false; 4538 4539 // Use ready instead of DOMContentLoaded 4540 if (name === "DOMContentLoaded") { 4541 name = "ready"; 4542 } 4543 4544 // DOM is already ready 4545 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4546 self.domLoaded = true; 4547 callback.call(scope, fix({type: name})); 4548 continue; 4549 } 4550 4551 // Handle mouseenter/mouseleaver 4552 if (!hasMouseEnterLeave) { 4553 fakeName = mouseEnterLeave[name]; 4554 4555 if (fakeName) { 4556 nativeHandler = function(evt) { 4557 var current, related; 4558 4559 current = evt.currentTarget; 4560 related = evt.relatedTarget; 4561 4562 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4563 if (related && current.contains) { 4564 // Use contains for performance 4565 related = current.contains(related); 4566 } else { 4567 while (related && related !== current) { 4568 related = related.parentNode; 4569 } 4570 } 4571 4572 // Fire fake event 4573 if (!related) { 4574 evt = fix(evt || win.event); 4575 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4576 evt.target = current; 4577 executeHandlers(evt, id); 4578 } 4579 }; 4580 } 4581 } 4582 4583 // Fake bubbeling of focusin/focusout 4584 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4585 capture = true; 4586 fakeName = name === "focusin" ? "focus" : "blur"; 4587 nativeHandler = function(evt) { 4588 evt = fix(evt || win.event); 4589 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4590 executeHandlers(evt, id); 4591 }; 4592 } 4593 4594 // Setup callback list and bind native event 4595 callbackList = events[id][name]; 4596 if (!callbackList) { 4597 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4598 callbackList.fakeName = fakeName; 4599 callbackList.capture = capture; 4600 4601 // Add the nativeHandler to the callback list so that we can later unbind it 4602 callbackList.nativeHandler = nativeHandler; 4603 if (!w3cEventModel) { 4604 callbackList.proxyHandler = proxy(id); 4605 } 4606 4607 // Check if the target has native events support 4608 if (name === "ready") { 4609 bindOnReady(target, nativeHandler, self); 4610 } else { 4611 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4612 } 4613 } else { 4614 // If it already has an native handler then just push the callback 4615 callbackList.push({func: callback, scope: scope}); 4616 } 4617 } 4618 4619 target = callbackList = 0; // Clean memory for IE 4620 4621 return callback; 4622 }; 4623 4624 self.unbind = function(target, names, callback) { 4625 var id, callbackList, i, ci, name, eventMap; 4626 4627 // Don't bind to text nodes or comments 4628 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4629 return self; 4630 } 4631 4632 // Unbind event or events if the target has the expando 4633 id = target[expando]; 4634 if (id) { 4635 eventMap = events[id]; 4636 4637 // Specific callback 4638 if (names) { 4639 names = names.split(' '); 4640 i = names.length; 4641 while (i--) { 4642 name = names[i]; 4643 callbackList = eventMap[name]; 4644 4645 // Unbind the event if it exists in the map 4646 if (callbackList) { 4647 // Remove specified callback 4648 if (callback) { 4649 ci = callbackList.length; 4650 while (ci--) { 4651 if (callbackList[ci].func === callback) { 4652 callbackList.splice(ci, 1); 4653 } 4654 } 4655 } 4656 4657 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4658 if (!callback || callbackList.length === 0) { 4659 delete eventMap[name]; 4660 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4661 } 4662 } 4663 } 4664 } else { 4665 // All events for a specific element 4666 for (name in eventMap) { 4667 callbackList = eventMap[name]; 4668 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4669 } 4670 4671 eventMap = {}; 4672 } 4673 4674 // Check if object is empty, if it isn't then we won't remove the expando map 4675 for (name in eventMap) { 4676 return self; 4677 } 4678 4679 // Delete event object 4680 delete events[id]; 4681 4682 // Remove expando from target 4683 try { 4684 // IE will fail here since it can't delete properties from window 4685 delete target[expando]; 4686 } catch (ex) { 4687 // IE will set it to null 4688 target[expando] = null; 4689 } 4690 } 4691 4692 return self; 4693 }; 4694 4695 self.fire = function(target, name, args) { 4696 var id, event; 4697 4698 // Don't bind to text nodes or comments 4699 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4700 return self; 4701 } 4702 4703 // Build event object by patching the args 4704 event = fix(null, args); 4705 event.type = name; 4706 4707 do { 4708 // Found an expando that means there is listeners to execute 4709 id = target[expando]; 4710 if (id) { 4711 executeHandlers(event, id); 4712 } 4713 4714 // Walk up the DOM 4715 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4716 } while (target && !event.isPropagationStopped()); 4717 4718 return self; 4719 }; 4720 4721 self.clean = function(target) { 4722 var i, children, unbind = self.unbind; 4723 4724 // Don't bind to text nodes or comments 4725 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4726 return self; 4727 } 4728 4729 // Unbind any element on the specificed target 4730 if (target[expando]) { 4731 unbind(target); 4732 } 4733 4734 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 4735 if (!target.getElementsByTagName) { 4736 target = target.document; 4737 } 4738 4739 // Remove events from each child element 4740 if (target && target.getElementsByTagName) { 4741 unbind(target); 4742 4743 children = target.getElementsByTagName('*'); 4744 i = children.length; 4745 while (i--) { 4746 target = children[i]; 4747 4748 if (target[expando]) { 4749 unbind(target); 4750 } 4751 } 4752 } 4753 4754 return self; 4755 }; 4756 4757 self.callNativeHandler = function(id, evt) { 4758 if (events) { 4759 events[id][evt.type].nativeHandler(evt); 4760 } 4761 }; 4762 4763 self.destory = function() { 4764 events = {}; 4765 }; 4766 4767 // Legacy function calls 4768 4769 self.add = function(target, events, func, scope) { 4770 // Old API supported direct ID assignment 4771 if (typeof(target) === "string") { 4772 target = document.getElementById(target); 4773 } 4774 4775 // Old API supported multiple targets 4776 if (target && target instanceof Array) { 4777 var i = target.length; 4778 4779 while (i--) { 4780 self.add(target[i], events, func, scope); 4781 } 4782 4783 return; 4784 } 4785 4786 // Old API called ready init 4787 if (events === "init") { 4788 events = "ready"; 4789 } 4790 4791 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 4792 }; 4793 4794 self.remove = function(target, events, func, scope) { 4795 if (!target) { 4796 return self; 4797 } 4798 4799 // Old API supported direct ID assignment 4800 if (typeof(target) === "string") { 4801 target = document.getElementById(target); 4802 } 4803 4804 // Old API supported multiple targets 4805 if (target instanceof Array) { 4806 var i = target.length; 4807 4808 while (i--) { 4809 self.remove(target[i], events, func, scope); 4810 } 4811 4812 return self; 4813 } 4814 4815 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 4816 }; 4817 4818 self.clear = function(target) { 4819 // Old API supported direct ID assignment 4820 if (typeof(target) === "string") { 4821 target = document.getElementById(target); 4822 } 4823 4824 return self.clean(target); 4825 }; 4826 4827 self.cancel = function(e) { 4828 if (e) { 4829 self.prevent(e); 4830 self.stop(e); 4831 } 4832 4833 return false; 4834 }; 4835 4836 self.prevent = function(e) { 4837 if (!e.preventDefault) { 4838 e = fix(e); 4839 } 4840 4841 e.preventDefault(); 4842 4843 return false; 4844 }; 4845 4846 self.stop = function(e) { 4847 if (!e.stopPropagation) { 4848 e = fix(e); 4849 } 4850 4851 e.stopPropagation(); 4852 4853 return false; 4854 }; 4855 } 4856 4857 namespace.EventUtils = EventUtils; 4858 4859 namespace.Event = new EventUtils(function(id) { 4860 return function(evt) { 4861 tinymce.dom.Event.callNativeHandler(id, evt); 4862 }; 4863 }); 4864 4865 // Bind ready event when tinymce script is loaded 4866 namespace.Event.bind(window, 'ready', function() {}); 4867 4868 namespace = 0; 4869 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 4870 4871 tinymce.dom.TreeWalker = function(start_node, root_node) { 4872 var node = start_node; 4873 4874 function findSibling(node, start_name, sibling_name, shallow) { 4875 var sibling, parent; 4876 4877 if (node) { 4878 // Walk into nodes if it has a start 4879 if (!shallow && node[start_name]) 4880 return node[start_name]; 4881 4882 // Return the sibling if it has one 4883 if (node != root_node) { 4884 sibling = node[sibling_name]; 4885 if (sibling) 4886 return sibling; 4887 4888 // Walk up the parents to look for siblings 4889 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 4890 sibling = parent[sibling_name]; 4891 if (sibling) 4892 return sibling; 4893 } 4894 } 4895 } 4896 }; 4897 4898 this.current = function() { 4899 return node; 4900 }; 4901 4902 this.next = function(shallow) { 4903 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 4904 }; 4905 4906 this.prev = function(shallow) { 4907 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 4908 }; 4909 }; 4910 4911 (function(tinymce) { 4912 // Shorten names 4913 var each = tinymce.each, 4914 is = tinymce.is, 4915 isWebKit = tinymce.isWebKit, 4916 isIE = tinymce.isIE, 4917 Entities = tinymce.html.Entities, 4918 simpleSelectorRe = /^([a-z0-9],?)+$/i, 4919 whiteSpaceRegExp = /^[ \t\r\n]*$/; 4920 4921 tinymce.create('tinymce.dom.DOMUtils', { 4922 doc : null, 4923 root : null, 4924 files : null, 4925 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 4926 props : { 4927 "for" : "htmlFor", 4928 "class" : "className", 4929 className : "className", 4930 checked : "checked", 4931 disabled : "disabled", 4932 maxlength : "maxLength", 4933 readonly : "readOnly", 4934 selected : "selected", 4935 value : "value", 4936 id : "id", 4937 name : "name", 4938 type : "type" 4939 }, 4940 4941 DOMUtils : function(d, s) { 4942 var t = this, globalStyle, name, blockElementsMap; 4943 4944 t.doc = d; 4945 t.win = window; 4946 t.files = {}; 4947 t.cssFlicker = false; 4948 t.counter = 0; 4949 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 4950 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 4951 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 4952 4953 t.settings = s = tinymce.extend({ 4954 keep_values : false, 4955 hex_colors : 1 4956 }, s); 4957 4958 t.schema = s.schema; 4959 t.styles = new tinymce.html.Styles({ 4960 url_converter : s.url_converter, 4961 url_converter_scope : s.url_converter_scope 4962 }, s.schema); 4963 4964 // Fix IE6SP2 flicker and check it failed for pre SP2 4965 if (tinymce.isIE6) { 4966 try { 4967 d.execCommand('BackgroundImageCache', false, true); 4968 } catch (e) { 4969 t.cssFlicker = true; 4970 } 4971 } 4972 4973 t.fixDoc(d); 4974 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 4975 tinymce.addUnload(t.destroy, t); 4976 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 4977 4978 t.isBlock = function(node) { 4979 // This function is called in module pattern style since it might be executed with the wrong this scope 4980 var type = node.nodeType; 4981 4982 // If it's a node then check the type and use the nodeName 4983 if (type) 4984 return !!(type === 1 && blockElementsMap[node.nodeName]); 4985 4986 return !!blockElementsMap[node]; 4987 }; 4988 }, 4989 4990 fixDoc: function(doc) { 4991 var settings = this.settings, name; 4992 4993 if (isIE && settings.schema) { 4994 // Add missing HTML 4/5 elements to IE 4995 ('abbr article aside audio canvas ' + 4996 'details figcaption figure footer ' + 4997 'header hgroup mark menu meter nav ' + 4998 'output progress section summary ' + 4999 'time video').replace(/\w+/g, function(name) { 5000 doc.createElement(name); 5001 }); 5002 5003 // Create all custom elements 5004 for (name in settings.schema.getCustomElements()) { 5005 doc.createElement(name); 5006 } 5007 } 5008 }, 5009 5010 clone: function(node, deep) { 5011 var self = this, clone, doc; 5012 5013 // TODO: Add feature detection here in the future 5014 if (!isIE || node.nodeType !== 1 || deep) { 5015 return node.cloneNode(deep); 5016 } 5017 5018 doc = self.doc; 5019 5020 // Make a HTML5 safe shallow copy 5021 if (!deep) { 5022 clone = doc.createElement(node.nodeName); 5023 5024 // Copy attribs 5025 each(self.getAttribs(node), function(attr) { 5026 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5027 }); 5028 5029 return clone; 5030 } 5031 /* 5032 // Setup HTML5 patched document fragment 5033 if (!self.frag) { 5034 self.frag = doc.createDocumentFragment(); 5035 self.fixDoc(self.frag); 5036 } 5037 5038 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5039 clone = doc.createElement('div'); 5040 self.frag.appendChild(clone); 5041 clone.innerHTML = node.outerHTML; 5042 self.frag.removeChild(clone); 5043 */ 5044 return clone.firstChild; 5045 }, 5046 5047 getRoot : function() { 5048 var t = this, s = t.settings; 5049 5050 return (s && t.get(s.root_element)) || t.doc.body; 5051 }, 5052 5053 getViewPort : function(w) { 5054 var d, b; 5055 5056 w = !w ? this.win : w; 5057 d = w.document; 5058 b = this.boxModel ? d.documentElement : d.body; 5059 5060 // Returns viewport size excluding scrollbars 5061 return { 5062 x : w.pageXOffset || b.scrollLeft, 5063 y : w.pageYOffset || b.scrollTop, 5064 w : w.innerWidth || b.clientWidth, 5065 h : w.innerHeight || b.clientHeight 5066 }; 5067 }, 5068 5069 getRect : function(e) { 5070 var p, t = this, sr; 5071 5072 e = t.get(e); 5073 p = t.getPos(e); 5074 sr = t.getSize(e); 5075 5076 return { 5077 x : p.x, 5078 y : p.y, 5079 w : sr.w, 5080 h : sr.h 5081 }; 5082 }, 5083 5084 getSize : function(e) { 5085 var t = this, w, h; 5086 5087 e = t.get(e); 5088 w = t.getStyle(e, 'width'); 5089 h = t.getStyle(e, 'height'); 5090 5091 // Non pixel value, then force offset/clientWidth 5092 if (w.indexOf('px') === -1) 5093 w = 0; 5094 5095 // Non pixel value, then force offset/clientWidth 5096 if (h.indexOf('px') === -1) 5097 h = 0; 5098 5099 return { 5100 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5101 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5102 }; 5103 }, 5104 5105 getParent : function(n, f, r) { 5106 return this.getParents(n, f, r, false); 5107 }, 5108 5109 getParents : function(n, f, r, c) { 5110 var t = this, na, se = t.settings, o = []; 5111 5112 n = t.get(n); 5113 c = c === undefined; 5114 5115 if (se.strict_root) 5116 r = r || t.getRoot(); 5117 5118 // Wrap node name as func 5119 if (is(f, 'string')) { 5120 na = f; 5121 5122 if (f === '*') { 5123 f = function(n) {return n.nodeType == 1;}; 5124 } else { 5125 f = function(n) { 5126 return t.is(n, na); 5127 }; 5128 } 5129 } 5130 5131 while (n) { 5132 if (n == r || !n.nodeType || n.nodeType === 9) 5133 break; 5134 5135 if (!f || f(n)) { 5136 if (c) 5137 o.push(n); 5138 else 5139 return n; 5140 } 5141 5142 n = n.parentNode; 5143 } 5144 5145 return c ? o : null; 5146 }, 5147 5148 get : function(e) { 5149 var n; 5150 5151 if (e && this.doc && typeof(e) == 'string') { 5152 n = e; 5153 e = this.doc.getElementById(e); 5154 5155 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5156 if (e && e.id !== n) 5157 return this.doc.getElementsByName(n)[1]; 5158 } 5159 5160 return e; 5161 }, 5162 5163 getNext : function(node, selector) { 5164 return this._findSib(node, selector, 'nextSibling'); 5165 }, 5166 5167 getPrev : function(node, selector) { 5168 return this._findSib(node, selector, 'previousSibling'); 5169 }, 5170 5171 5172 select : function(pa, s) { 5173 var t = this; 5174 5175 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5176 }, 5177 5178 is : function(n, selector) { 5179 var i; 5180 5181 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5182 if (n.length === undefined) { 5183 // Simple all selector 5184 if (selector === '*') 5185 return n.nodeType == 1; 5186 5187 // Simple selector just elements 5188 if (simpleSelectorRe.test(selector)) { 5189 selector = selector.toLowerCase().split(/,/); 5190 n = n.nodeName.toLowerCase(); 5191 5192 for (i = selector.length - 1; i >= 0; i--) { 5193 if (selector[i] == n) 5194 return true; 5195 } 5196 5197 return false; 5198 } 5199 } 5200 5201 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5202 }, 5203 5204 5205 add : function(p, n, a, h, c) { 5206 var t = this; 5207 5208 return this.run(p, function(p) { 5209 var e, k; 5210 5211 e = is(n, 'string') ? t.doc.createElement(n) : n; 5212 t.setAttribs(e, a); 5213 5214 if (h) { 5215 if (h.nodeType) 5216 e.appendChild(h); 5217 else 5218 t.setHTML(e, h); 5219 } 5220 5221 return !c ? p.appendChild(e) : e; 5222 }); 5223 }, 5224 5225 create : function(n, a, h) { 5226 return this.add(this.doc.createElement(n), n, a, h, 1); 5227 }, 5228 5229 createHTML : function(n, a, h) { 5230 var o = '', t = this, k; 5231 5232 o += '<' + n; 5233 5234 for (k in a) { 5235 if (a.hasOwnProperty(k)) 5236 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5237 } 5238 5239 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5240 if (typeof(h) != "undefined") 5241 return o + '>' + h + '</' + n + '>'; 5242 5243 return o + ' />'; 5244 }, 5245 5246 remove : function(node, keep_children) { 5247 return this.run(node, function(node) { 5248 var child, parent = node.parentNode; 5249 5250 if (!parent) 5251 return null; 5252 5253 if (keep_children) { 5254 while (child = node.firstChild) { 5255 // IE 8 will crash if you don't remove completely empty text nodes 5256 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5257 parent.insertBefore(child, node); 5258 else 5259 node.removeChild(child); 5260 } 5261 } 5262 5263 return parent.removeChild(node); 5264 }); 5265 }, 5266 5267 setStyle : function(n, na, v) { 5268 var t = this; 5269 5270 return t.run(n, function(e) { 5271 var s, i; 5272 5273 s = e.style; 5274 5275 // Camelcase it, if needed 5276 na = na.replace(/-(\D)/g, function(a, b){ 5277 return b.toUpperCase(); 5278 }); 5279 5280 // Default px suffix on these 5281 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5282 v += 'px'; 5283 5284 switch (na) { 5285 case 'opacity': 5286 // IE specific opacity 5287 if (isIE) { 5288 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5289 5290 if (!n.currentStyle || !n.currentStyle.hasLayout) 5291 s.display = 'inline-block'; 5292 } 5293 5294 // Fix for older browsers 5295 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5296 break; 5297 5298 case 'float': 5299 isIE ? s.styleFloat = v : s.cssFloat = v; 5300 break; 5301 5302 default: 5303 s[na] = v || ''; 5304 } 5305 5306 // Force update of the style data 5307 if (t.settings.update_styles) 5308 t.setAttrib(e, 'data-mce-style'); 5309 }); 5310 }, 5311 5312 getStyle : function(n, na, c) { 5313 n = this.get(n); 5314 5315 if (!n) 5316 return; 5317 5318 // Gecko 5319 if (this.doc.defaultView && c) { 5320 // Remove camelcase 5321 na = na.replace(/[A-Z]/g, function(a){ 5322 return '-' + a; 5323 }); 5324 5325 try { 5326 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5327 } catch (ex) { 5328 // Old safari might fail 5329 return null; 5330 } 5331 } 5332 5333 // Camelcase it, if needed 5334 na = na.replace(/-(\D)/g, function(a, b){ 5335 return b.toUpperCase(); 5336 }); 5337 5338 if (na == 'float') 5339 na = isIE ? 'styleFloat' : 'cssFloat'; 5340 5341 // IE & Opera 5342 if (n.currentStyle && c) 5343 return n.currentStyle[na]; 5344 5345 return n.style ? n.style[na] : undefined; 5346 }, 5347 5348 setStyles : function(e, o) { 5349 var t = this, s = t.settings, ol; 5350 5351 ol = s.update_styles; 5352 s.update_styles = 0; 5353 5354 each(o, function(v, n) { 5355 t.setStyle(e, n, v); 5356 }); 5357 5358 // Update style info 5359 s.update_styles = ol; 5360 if (s.update_styles) 5361 t.setAttrib(e, s.cssText); 5362 }, 5363 5364 removeAllAttribs: function(e) { 5365 return this.run(e, function(e) { 5366 var i, attrs = e.attributes; 5367 for (i = attrs.length - 1; i >= 0; i--) { 5368 e.removeAttributeNode(attrs.item(i)); 5369 } 5370 }); 5371 }, 5372 5373 setAttrib : function(e, n, v) { 5374 var t = this; 5375 5376 // Whats the point 5377 if (!e || !n) 5378 return; 5379 5380 // Strict XML mode 5381 if (t.settings.strict) 5382 n = n.toLowerCase(); 5383 5384 return this.run(e, function(e) { 5385 var s = t.settings; 5386 var originalValue = e.getAttribute(n); 5387 if (v !== null) { 5388 switch (n) { 5389 case "style": 5390 if (!is(v, 'string')) { 5391 each(v, function(v, n) { 5392 t.setStyle(e, n, v); 5393 }); 5394 5395 return; 5396 } 5397 5398 // No mce_style for elements with these since they might get resized by the user 5399 if (s.keep_values) { 5400 if (v && !t._isRes(v)) 5401 e.setAttribute('data-mce-style', v, 2); 5402 else 5403 e.removeAttribute('data-mce-style', 2); 5404 } 5405 5406 e.style.cssText = v; 5407 break; 5408 5409 case "class": 5410 e.className = v || ''; // Fix IE null bug 5411 break; 5412 5413 case "src": 5414 case "href": 5415 if (s.keep_values) { 5416 if (s.url_converter) 5417 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5418 5419 t.setAttrib(e, 'data-mce-' + n, v, 2); 5420 } 5421 5422 break; 5423 5424 case "shape": 5425 e.setAttribute('data-mce-style', v); 5426 break; 5427 } 5428 } 5429 if (is(v) && v !== null && v.length !== 0) 5430 e.setAttribute(n, '' + v, 2); 5431 else 5432 e.removeAttribute(n, 2); 5433 5434 // fire onChangeAttrib event for attributes that have changed 5435 if (tinyMCE.activeEditor && originalValue != v) { 5436 var ed = tinyMCE.activeEditor; 5437 ed.onSetAttrib.dispatch(ed, e, n, v); 5438 } 5439 }); 5440 }, 5441 5442 setAttribs : function(e, o) { 5443 var t = this; 5444 5445 return this.run(e, function(e) { 5446 each(o, function(v, n) { 5447 t.setAttrib(e, n, v); 5448 }); 5449 }); 5450 }, 5451 5452 getAttrib : function(e, n, dv) { 5453 var v, t = this, undef; 5454 5455 e = t.get(e); 5456 5457 if (!e || e.nodeType !== 1) 5458 return dv === undef ? false : dv; 5459 5460 if (!is(dv)) 5461 dv = ''; 5462 5463 // Try the mce variant for these 5464 if (/^(src|href|style|coords|shape)$/.test(n)) { 5465 v = e.getAttribute("data-mce-" + n); 5466 5467 if (v) 5468 return v; 5469 } 5470 5471 if (isIE && t.props[n]) { 5472 v = e[t.props[n]]; 5473 v = v && v.nodeValue ? v.nodeValue : v; 5474 } 5475 5476 if (!v) 5477 v = e.getAttribute(n, 2); 5478 5479 // Check boolean attribs 5480 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5481 if (e[t.props[n]] === true && v === '') 5482 return n; 5483 5484 return v ? n : ''; 5485 } 5486 5487 // Inner input elements will override attributes on form elements 5488 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5489 return e.getAttributeNode(n).nodeValue; 5490 5491 if (n === 'style') { 5492 v = v || e.style.cssText; 5493 5494 if (v) { 5495 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5496 5497 if (t.settings.keep_values && !t._isRes(v)) 5498 e.setAttribute('data-mce-style', v); 5499 } 5500 } 5501 5502 // Remove Apple and WebKit stuff 5503 if (isWebKit && n === "class" && v) 5504 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5505 5506 // Handle IE issues 5507 if (isIE) { 5508 switch (n) { 5509 case 'rowspan': 5510 case 'colspan': 5511 // IE returns 1 as default value 5512 if (v === 1) 5513 v = ''; 5514 5515 break; 5516 5517 case 'size': 5518 // IE returns +0 as default value for size 5519 if (v === '+0' || v === 20 || v === 0) 5520 v = ''; 5521 5522 break; 5523 5524 case 'width': 5525 case 'height': 5526 case 'vspace': 5527 case 'checked': 5528 case 'disabled': 5529 case 'readonly': 5530 if (v === 0) 5531 v = ''; 5532 5533 break; 5534 5535 case 'hspace': 5536 // IE returns -1 as default value 5537 if (v === -1) 5538 v = ''; 5539 5540 break; 5541 5542 case 'maxlength': 5543 case 'tabindex': 5544 // IE returns default value 5545 if (v === 32768 || v === 2147483647 || v === '32768') 5546 v = ''; 5547 5548 break; 5549 5550 case 'multiple': 5551 case 'compact': 5552 case 'noshade': 5553 case 'nowrap': 5554 if (v === 65535) 5555 return n; 5556 5557 return dv; 5558 5559 case 'shape': 5560 v = v.toLowerCase(); 5561 break; 5562 5563 default: 5564 // IE has odd anonymous function for event attributes 5565 if (n.indexOf('on') === 0 && v) 5566 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5567 } 5568 } 5569 5570 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5571 }, 5572 5573 getPos : function(n, ro) { 5574 var t = this, x = 0, y = 0, e, d = t.doc, r; 5575 5576 n = t.get(n); 5577 ro = ro || d.body; 5578 5579 if (n) { 5580 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5581 if (n.getBoundingClientRect) { 5582 n = n.getBoundingClientRect(); 5583 e = t.boxModel ? d.documentElement : d.body; 5584 5585 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5586 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5587 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5588 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5589 5590 return {x : x, y : y}; 5591 } 5592 5593 r = n; 5594 while (r && r != ro && r.nodeType) { 5595 x += r.offsetLeft || 0; 5596 y += r.offsetTop || 0; 5597 r = r.offsetParent; 5598 } 5599 5600 r = n.parentNode; 5601 while (r && r != ro && r.nodeType) { 5602 x -= r.scrollLeft || 0; 5603 y -= r.scrollTop || 0; 5604 r = r.parentNode; 5605 } 5606 } 5607 5608 return {x : x, y : y}; 5609 }, 5610 5611 parseStyle : function(st) { 5612 return this.styles.parse(st); 5613 }, 5614 5615 serializeStyle : function(o, name) { 5616 return this.styles.serialize(o, name); 5617 }, 5618 5619 addStyle: function(cssText) { 5620 var doc = this.doc, head; 5621 5622 // Create style element if needed 5623 styleElm = doc.getElementById('mceDefaultStyles'); 5624 if (!styleElm) { 5625 styleElm = doc.createElement('style'), 5626 styleElm.id = 'mceDefaultStyles'; 5627 styleElm.type = 'text/css'; 5628 5629 head = doc.getElementsByTagName('head')[0] 5630 if (head.firstChild) { 5631 head.insertBefore(styleElm, head.firstChild); 5632 } else { 5633 head.appendChild(styleElm); 5634 } 5635 } 5636 5637 // Append style data to old or new style element 5638 if (styleElm.styleSheet) { 5639 styleElm.styleSheet.cssText += cssText; 5640 } else { 5641 styleElm.appendChild(doc.createTextNode(cssText)); 5642 } 5643 }, 5644 5645 loadCSS : function(u) { 5646 var t = this, d = t.doc, head; 5647 5648 if (!u) 5649 u = ''; 5650 5651 head = d.getElementsByTagName('head')[0]; 5652 5653 each(u.split(','), function(u) { 5654 var link; 5655 5656 if (t.files[u]) 5657 return; 5658 5659 t.files[u] = true; 5660 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5661 5662 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5663 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5664 // It's ugly but it seems to work fine. 5665 if (isIE && d.documentMode && d.recalc) { 5666 link.onload = function() { 5667 if (d.recalc) 5668 d.recalc(); 5669 5670 link.onload = null; 5671 }; 5672 } 5673 5674 head.appendChild(link); 5675 }); 5676 }, 5677 5678 addClass : function(e, c) { 5679 return this.run(e, function(e) { 5680 var o; 5681 5682 if (!c) 5683 return 0; 5684 5685 if (this.hasClass(e, c)) 5686 return e.className; 5687 5688 o = this.removeClass(e, c); 5689 5690 return e.className = (o != '' ? (o + ' ') : '') + c; 5691 }); 5692 }, 5693 5694 removeClass : function(e, c) { 5695 var t = this, re; 5696 5697 return t.run(e, function(e) { 5698 var v; 5699 5700 if (t.hasClass(e, c)) { 5701 if (!re) 5702 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5703 5704 v = e.className.replace(re, ' '); 5705 v = tinymce.trim(v != ' ' ? v : ''); 5706 5707 e.className = v; 5708 5709 // Empty class attr 5710 if (!v) { 5711 e.removeAttribute('class'); 5712 e.removeAttribute('className'); 5713 } 5714 5715 return v; 5716 } 5717 5718 return e.className; 5719 }); 5720 }, 5721 5722 hasClass : function(n, c) { 5723 n = this.get(n); 5724 5725 if (!n || !c) 5726 return false; 5727 5728 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5729 }, 5730 5731 show : function(e) { 5732 return this.setStyle(e, 'display', 'block'); 5733 }, 5734 5735 hide : function(e) { 5736 return this.setStyle(e, 'display', 'none'); 5737 }, 5738 5739 isHidden : function(e) { 5740 e = this.get(e); 5741 5742 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 5743 }, 5744 5745 uniqueId : function(p) { 5746 return (!p ? 'mce_' : p) + (this.counter++); 5747 }, 5748 5749 setHTML : function(element, html) { 5750 var self = this; 5751 5752 return self.run(element, function(element) { 5753 if (isIE) { 5754 // Remove all child nodes, IE keeps empty text nodes in DOM 5755 while (element.firstChild) 5756 element.removeChild(element.firstChild); 5757 5758 try { 5759 // IE will remove comments from the beginning 5760 // unless you padd the contents with something 5761 element.innerHTML = '<br />' + html; 5762 element.removeChild(element.firstChild); 5763 } catch (ex) { 5764 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p 5765 // This seems to fix this problem 5766 5767 // Create new div with HTML contents and a BR infront to keep comments 5768 var newElement = self.create('div'); 5769 newElement.innerHTML = '<br />' + html; 5770 5771 // Add all children from div to target 5772 each (tinymce.grep(newElement.childNodes), function(node, i) { 5773 // Skip br element 5774 if (i && element.canHaveHTML) 5775 element.appendChild(node); 5776 }); 5777 } 5778 } else 5779 element.innerHTML = html; 5780 5781 return html; 5782 }); 5783 }, 5784 5785 getOuterHTML : function(elm) { 5786 var doc, self = this; 5787 5788 elm = self.get(elm); 5789 5790 if (!elm) 5791 return null; 5792 5793 if (elm.nodeType === 1 && self.hasOuterHTML) 5794 return elm.outerHTML; 5795 5796 doc = (elm.ownerDocument || self.doc).createElement("body"); 5797 doc.appendChild(elm.cloneNode(true)); 5798 5799 return doc.innerHTML; 5800 }, 5801 5802 setOuterHTML : function(e, h, d) { 5803 var t = this; 5804 5805 function setHTML(e, h, d) { 5806 var n, tp; 5807 5808 tp = d.createElement("body"); 5809 tp.innerHTML = h; 5810 5811 n = tp.lastChild; 5812 while (n) { 5813 t.insertAfter(n.cloneNode(true), e); 5814 n = n.previousSibling; 5815 } 5816 5817 t.remove(e); 5818 }; 5819 5820 return this.run(e, function(e) { 5821 e = t.get(e); 5822 5823 // Only set HTML on elements 5824 if (e.nodeType == 1) { 5825 d = d || e.ownerDocument || t.doc; 5826 5827 if (isIE) { 5828 try { 5829 // Try outerHTML for IE it sometimes produces an unknown runtime error 5830 if (isIE && e.nodeType == 1) 5831 e.outerHTML = h; 5832 else 5833 setHTML(e, h, d); 5834 } catch (ex) { 5835 // Fix for unknown runtime error 5836 setHTML(e, h, d); 5837 } 5838 } else 5839 setHTML(e, h, d); 5840 } 5841 }); 5842 }, 5843 5844 decode : Entities.decode, 5845 5846 encode : Entities.encodeAllRaw, 5847 5848 insertAfter : function(node, reference_node) { 5849 reference_node = this.get(reference_node); 5850 5851 return this.run(node, function(node) { 5852 var parent, nextSibling; 5853 5854 parent = reference_node.parentNode; 5855 nextSibling = reference_node.nextSibling; 5856 5857 if (nextSibling) 5858 parent.insertBefore(node, nextSibling); 5859 else 5860 parent.appendChild(node); 5861 5862 return node; 5863 }); 5864 }, 5865 5866 replace : function(n, o, k) { 5867 var t = this; 5868 5869 if (is(o, 'array')) 5870 n = n.cloneNode(true); 5871 5872 return t.run(o, function(o) { 5873 if (k) { 5874 each(tinymce.grep(o.childNodes), function(c) { 5875 n.appendChild(c); 5876 }); 5877 } 5878 5879 return o.parentNode.replaceChild(n, o); 5880 }); 5881 }, 5882 5883 rename : function(elm, name) { 5884 var t = this, newElm; 5885 5886 if (elm.nodeName != name.toUpperCase()) { 5887 // Rename block element 5888 newElm = t.create(name); 5889 5890 // Copy attribs to new block 5891 each(t.getAttribs(elm), function(attr_node) { 5892 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 5893 }); 5894 5895 // Replace block 5896 t.replace(newElm, elm, 1); 5897 } 5898 5899 return newElm || elm; 5900 }, 5901 5902 findCommonAncestor : function(a, b) { 5903 var ps = a, pe; 5904 5905 while (ps) { 5906 pe = b; 5907 5908 while (pe && ps != pe) 5909 pe = pe.parentNode; 5910 5911 if (ps == pe) 5912 break; 5913 5914 ps = ps.parentNode; 5915 } 5916 5917 if (!ps && a.ownerDocument) 5918 return a.ownerDocument.documentElement; 5919 5920 return ps; 5921 }, 5922 5923 toHex : function(s) { 5924 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 5925 5926 function hex(s) { 5927 s = parseInt(s, 10).toString(16); 5928 5929 return s.length > 1 ? s : '0' + s; // 0 -> 00 5930 }; 5931 5932 if (c) { 5933 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 5934 5935 return s; 5936 } 5937 5938 return s; 5939 }, 5940 5941 getClasses : function() { 5942 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 5943 5944 if (t.classes) 5945 return t.classes; 5946 5947 function addClasses(s) { 5948 // IE style imports 5949 each(s.imports, function(r) { 5950 addClasses(r); 5951 }); 5952 5953 each(s.cssRules || s.rules, function(r) { 5954 // Real type or fake it on IE 5955 switch (r.type || 1) { 5956 // Rule 5957 case 1: 5958 if (r.selectorText) { 5959 each(r.selectorText.split(','), function(v) { 5960 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 5961 5962 // Is internal or it doesn't contain a class 5963 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 5964 return; 5965 5966 // Remove everything but class name 5967 ov = v; 5968 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 5969 5970 // Filter classes 5971 if (f && !(v = f(v, ov))) 5972 return; 5973 5974 if (!lo[v]) { 5975 cl.push({'class' : v}); 5976 lo[v] = 1; 5977 } 5978 }); 5979 } 5980 break; 5981 5982 // Import 5983 case 3: 5984 addClasses(r.styleSheet); 5985 break; 5986 } 5987 }); 5988 }; 5989 5990 try { 5991 each(t.doc.styleSheets, addClasses); 5992 } catch (ex) { 5993 // Ignore 5994 } 5995 5996 if (cl.length > 0) 5997 t.classes = cl; 5998 5999 return cl; 6000 }, 6001 6002 run : function(e, f, s) { 6003 var t = this, o; 6004 6005 if (t.doc && typeof(e) === 'string') 6006 e = t.get(e); 6007 6008 if (!e) 6009 return false; 6010 6011 s = s || this; 6012 if (!e.nodeType && (e.length || e.length === 0)) { 6013 o = []; 6014 6015 each(e, function(e, i) { 6016 if (e) { 6017 if (typeof(e) == 'string') 6018 e = t.doc.getElementById(e); 6019 6020 o.push(f.call(s, e, i)); 6021 } 6022 }); 6023 6024 return o; 6025 } 6026 6027 return f.call(s, e); 6028 }, 6029 6030 getAttribs : function(n) { 6031 var o; 6032 6033 n = this.get(n); 6034 6035 if (!n) 6036 return []; 6037 6038 if (isIE) { 6039 o = []; 6040 6041 // Object will throw exception in IE 6042 if (n.nodeName == 'OBJECT') 6043 return n.attributes; 6044 6045 // IE doesn't keep the selected attribute if you clone option elements 6046 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6047 o.push({specified : 1, nodeName : 'selected'}); 6048 6049 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6050 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6051 o.push({specified : 1, nodeName : a}); 6052 }); 6053 6054 return o; 6055 } 6056 6057 return n.attributes; 6058 }, 6059 6060 isEmpty : function(node, elements) { 6061 var self = this, i, attributes, type, walker, name, brCount = 0; 6062 6063 node = node.firstChild; 6064 if (node) { 6065 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6066 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6067 6068 do { 6069 type = node.nodeType; 6070 6071 if (type === 1) { 6072 // Ignore bogus elements 6073 if (node.getAttribute('data-mce-bogus')) 6074 continue; 6075 6076 // Keep empty elements like <img /> 6077 name = node.nodeName.toLowerCase(); 6078 if (elements && elements[name]) { 6079 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6080 if (name === 'br') { 6081 brCount++; 6082 continue; 6083 } 6084 6085 return false; 6086 } 6087 6088 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6089 attributes = self.getAttribs(node); 6090 i = node.attributes.length; 6091 while (i--) { 6092 name = node.attributes[i].nodeName; 6093 if (name === "name" || name === 'data-mce-bookmark') 6094 return false; 6095 } 6096 } 6097 6098 // Keep comment nodes 6099 if (type == 8) 6100 return false; 6101 6102 // Keep non whitespace text nodes 6103 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6104 return false; 6105 } while (node = walker.next()); 6106 } 6107 6108 return brCount <= 1; 6109 }, 6110 6111 destroy : function(s) { 6112 var t = this; 6113 6114 t.win = t.doc = t.root = t.events = t.frag = null; 6115 6116 // Manual destroy then remove unload handler 6117 if (!s) 6118 tinymce.removeUnload(t.destroy); 6119 }, 6120 6121 createRng : function() { 6122 var d = this.doc; 6123 6124 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6125 }, 6126 6127 nodeIndex : function(node, normalized) { 6128 var idx = 0, lastNodeType, lastNode, nodeType; 6129 6130 if (node) { 6131 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6132 nodeType = node.nodeType; 6133 6134 // Normalize text nodes 6135 if (normalized && nodeType == 3) { 6136 if (nodeType == lastNodeType || !node.nodeValue.length) 6137 continue; 6138 } 6139 idx++; 6140 lastNodeType = nodeType; 6141 } 6142 } 6143 6144 return idx; 6145 }, 6146 6147 split : function(pe, e, re) { 6148 var t = this, r = t.createRng(), bef, aft, pa; 6149 6150 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6151 // but we don't want that in our code since it serves no purpose for the end user 6152 // For example if this is chopped: 6153 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6154 // would produce: 6155 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6156 // this function will then trim of empty edges and produce: 6157 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6158 function trim(node) { 6159 var i, children = node.childNodes, type = node.nodeType; 6160 6161 function surroundedBySpans(node) { 6162 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6163 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6164 return previousIsSpan && nextIsSpan; 6165 } 6166 6167 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6168 return; 6169 6170 for (i = children.length - 1; i >= 0; i--) 6171 trim(children[i]); 6172 6173 if (type != 9) { 6174 // Keep non whitespace text nodes 6175 if (type == 3 && node.nodeValue.length > 0) { 6176 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6177 // Also keep text nodes with only spaces if surrounded by spans. 6178 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6179 var trimmedLength = tinymce.trim(node.nodeValue).length; 6180 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6181 return; 6182 } else if (type == 1) { 6183 // If the only child is a bookmark then move it up 6184 children = node.childNodes; 6185 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6186 node.parentNode.insertBefore(children[0], node); 6187 6188 // Keep non empty elements or img, hr etc 6189 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6190 return; 6191 } 6192 6193 t.remove(node); 6194 } 6195 6196 return node; 6197 }; 6198 6199 if (pe && e) { 6200 // Get before chunk 6201 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6202 r.setEnd(e.parentNode, t.nodeIndex(e)); 6203 bef = r.extractContents(); 6204 6205 // Get after chunk 6206 r = t.createRng(); 6207 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6208 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6209 aft = r.extractContents(); 6210 6211 // Insert before chunk 6212 pa = pe.parentNode; 6213 pa.insertBefore(trim(bef), pe); 6214 6215 // Insert middle chunk 6216 if (re) 6217 pa.replaceChild(re, e); 6218 else 6219 pa.insertBefore(e, pe); 6220 6221 // Insert after chunk 6222 pa.insertBefore(trim(aft), pe); 6223 t.remove(pe); 6224 6225 return re || e; 6226 } 6227 }, 6228 6229 bind : function(target, name, func, scope) { 6230 return this.events.add(target, name, func, scope || this); 6231 }, 6232 6233 unbind : function(target, name, func) { 6234 return this.events.remove(target, name, func); 6235 }, 6236 6237 fire : function(target, name, evt) { 6238 return this.events.fire(target, name, evt); 6239 }, 6240 6241 // Returns the content editable state of a node 6242 getContentEditable: function(node) { 6243 var contentEditable; 6244 6245 // Check type 6246 if (node.nodeType != 1) { 6247 return null; 6248 } 6249 6250 // Check for fake content editable 6251 contentEditable = node.getAttribute("data-mce-contenteditable"); 6252 if (contentEditable && contentEditable !== "inherit") { 6253 return contentEditable; 6254 } 6255 6256 // Check for real content editable 6257 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6258 }, 6259 6260 6261 _findSib : function(node, selector, name) { 6262 var t = this, f = selector; 6263 6264 if (node) { 6265 // If expression make a function of it using is 6266 if (is(f, 'string')) { 6267 f = function(node) { 6268 return t.is(node, selector); 6269 }; 6270 } 6271 6272 // Loop all siblings 6273 for (node = node[name]; node; node = node[name]) { 6274 if (f(node)) 6275 return node; 6276 } 6277 } 6278 6279 return null; 6280 }, 6281 6282 _isRes : function(c) { 6283 // Is live resizble element 6284 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6285 } 6286 6287 /* 6288 walk : function(n, f, s) { 6289 var d = this.doc, w; 6290 6291 if (d.createTreeWalker) { 6292 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6293 6294 while ((n = w.nextNode()) != null) 6295 f.call(s || this, n); 6296 } else 6297 tinymce.walk(n, f, 'childNodes', s); 6298 } 6299 */ 6300 6301 /* 6302 toRGB : function(s) { 6303 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6304 6305 if (c) { 6306 // #FFF -> #FFFFFF 6307 if (!is(c[3])) 6308 c[3] = c[2] = c[1]; 6309 6310 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6311 } 6312 6313 return s; 6314 } 6315 */ 6316 }); 6317 6318 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6319 })(tinymce); 6320 6321 (function(ns) { 6322 // Range constructor 6323 function Range(dom) { 6324 var t = this, 6325 doc = dom.doc, 6326 EXTRACT = 0, 6327 CLONE = 1, 6328 DELETE = 2, 6329 TRUE = true, 6330 FALSE = false, 6331 START_OFFSET = 'startOffset', 6332 START_CONTAINER = 'startContainer', 6333 END_CONTAINER = 'endContainer', 6334 END_OFFSET = 'endOffset', 6335 extend = tinymce.extend, 6336 nodeIndex = dom.nodeIndex; 6337 6338 extend(t, { 6339 // Inital states 6340 startContainer : doc, 6341 startOffset : 0, 6342 endContainer : doc, 6343 endOffset : 0, 6344 collapsed : TRUE, 6345 commonAncestorContainer : doc, 6346 6347 // Range constants 6348 START_TO_START : 0, 6349 START_TO_END : 1, 6350 END_TO_END : 2, 6351 END_TO_START : 3, 6352 6353 // Public methods 6354 setStart : setStart, 6355 setEnd : setEnd, 6356 setStartBefore : setStartBefore, 6357 setStartAfter : setStartAfter, 6358 setEndBefore : setEndBefore, 6359 setEndAfter : setEndAfter, 6360 collapse : collapse, 6361 selectNode : selectNode, 6362 selectNodeContents : selectNodeContents, 6363 compareBoundaryPoints : compareBoundaryPoints, 6364 deleteContents : deleteContents, 6365 extractContents : extractContents, 6366 cloneContents : cloneContents, 6367 insertNode : insertNode, 6368 surroundContents : surroundContents, 6369 cloneRange : cloneRange, 6370 toStringIE : toStringIE 6371 }); 6372 6373 function createDocumentFragment() { 6374 return doc.createDocumentFragment(); 6375 }; 6376 6377 function setStart(n, o) { 6378 _setEndPoint(TRUE, n, o); 6379 }; 6380 6381 function setEnd(n, o) { 6382 _setEndPoint(FALSE, n, o); 6383 }; 6384 6385 function setStartBefore(n) { 6386 setStart(n.parentNode, nodeIndex(n)); 6387 }; 6388 6389 function setStartAfter(n) { 6390 setStart(n.parentNode, nodeIndex(n) + 1); 6391 }; 6392 6393 function setEndBefore(n) { 6394 setEnd(n.parentNode, nodeIndex(n)); 6395 }; 6396 6397 function setEndAfter(n) { 6398 setEnd(n.parentNode, nodeIndex(n) + 1); 6399 }; 6400 6401 function collapse(ts) { 6402 if (ts) { 6403 t[END_CONTAINER] = t[START_CONTAINER]; 6404 t[END_OFFSET] = t[START_OFFSET]; 6405 } else { 6406 t[START_CONTAINER] = t[END_CONTAINER]; 6407 t[START_OFFSET] = t[END_OFFSET]; 6408 } 6409 6410 t.collapsed = TRUE; 6411 }; 6412 6413 function selectNode(n) { 6414 setStartBefore(n); 6415 setEndAfter(n); 6416 }; 6417 6418 function selectNodeContents(n) { 6419 setStart(n, 0); 6420 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6421 }; 6422 6423 function compareBoundaryPoints(h, r) { 6424 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6425 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6426 6427 // Check START_TO_START 6428 if (h === 0) 6429 return _compareBoundaryPoints(sc, so, rsc, rso); 6430 6431 // Check START_TO_END 6432 if (h === 1) 6433 return _compareBoundaryPoints(ec, eo, rsc, rso); 6434 6435 // Check END_TO_END 6436 if (h === 2) 6437 return _compareBoundaryPoints(ec, eo, rec, reo); 6438 6439 // Check END_TO_START 6440 if (h === 3) 6441 return _compareBoundaryPoints(sc, so, rec, reo); 6442 }; 6443 6444 function deleteContents() { 6445 _traverse(DELETE); 6446 }; 6447 6448 function extractContents() { 6449 return _traverse(EXTRACT); 6450 }; 6451 6452 function cloneContents() { 6453 return _traverse(CLONE); 6454 }; 6455 6456 function insertNode(n) { 6457 var startContainer = this[START_CONTAINER], 6458 startOffset = this[START_OFFSET], nn, o; 6459 6460 // Node is TEXT_NODE or CDATA 6461 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6462 if (!startOffset) { 6463 // At the start of text 6464 startContainer.parentNode.insertBefore(n, startContainer); 6465 } else if (startOffset >= startContainer.nodeValue.length) { 6466 // At the end of text 6467 dom.insertAfter(n, startContainer); 6468 } else { 6469 // Middle, need to split 6470 nn = startContainer.splitText(startOffset); 6471 startContainer.parentNode.insertBefore(n, nn); 6472 } 6473 } else { 6474 // Insert element node 6475 if (startContainer.childNodes.length > 0) 6476 o = startContainer.childNodes[startOffset]; 6477 6478 if (o) 6479 startContainer.insertBefore(n, o); 6480 else 6481 startContainer.appendChild(n); 6482 } 6483 }; 6484 6485 function surroundContents(n) { 6486 var f = t.extractContents(); 6487 6488 t.insertNode(n); 6489 n.appendChild(f); 6490 t.selectNode(n); 6491 }; 6492 6493 function cloneRange() { 6494 return extend(new Range(dom), { 6495 startContainer : t[START_CONTAINER], 6496 startOffset : t[START_OFFSET], 6497 endContainer : t[END_CONTAINER], 6498 endOffset : t[END_OFFSET], 6499 collapsed : t.collapsed, 6500 commonAncestorContainer : t.commonAncestorContainer 6501 }); 6502 }; 6503 6504 // Private methods 6505 6506 function _getSelectedNode(container, offset) { 6507 var child; 6508 6509 if (container.nodeType == 3 /* TEXT_NODE */) 6510 return container; 6511 6512 if (offset < 0) 6513 return container; 6514 6515 child = container.firstChild; 6516 while (child && offset > 0) { 6517 --offset; 6518 child = child.nextSibling; 6519 } 6520 6521 if (child) 6522 return child; 6523 6524 return container; 6525 }; 6526 6527 function _isCollapsed() { 6528 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6529 }; 6530 6531 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6532 var c, offsetC, n, cmnRoot, childA, childB; 6533 6534 // In the first case the boundary-points have the same container. A is before B 6535 // if its offset is less than the offset of B, A is equal to B if its offset is 6536 // equal to the offset of B, and A is after B if its offset is greater than the 6537 // offset of B. 6538 if (containerA == containerB) { 6539 if (offsetA == offsetB) 6540 return 0; // equal 6541 6542 if (offsetA < offsetB) 6543 return -1; // before 6544 6545 return 1; // after 6546 } 6547 6548 // In the second case a child node C of the container of A is an ancestor 6549 // container of B. In this case, A is before B if the offset of A is less than or 6550 // equal to the index of the child node C and A is after B otherwise. 6551 c = containerB; 6552 while (c && c.parentNode != containerA) 6553 c = c.parentNode; 6554 6555 if (c) { 6556 offsetC = 0; 6557 n = containerA.firstChild; 6558 6559 while (n != c && offsetC < offsetA) { 6560 offsetC++; 6561 n = n.nextSibling; 6562 } 6563 6564 if (offsetA <= offsetC) 6565 return -1; // before 6566 6567 return 1; // after 6568 } 6569 6570 // In the third case a child node C of the container of B is an ancestor container 6571 // of A. In this case, A is before B if the index of the child node C is less than 6572 // the offset of B and A is after B otherwise. 6573 c = containerA; 6574 while (c && c.parentNode != containerB) { 6575 c = c.parentNode; 6576 } 6577 6578 if (c) { 6579 offsetC = 0; 6580 n = containerB.firstChild; 6581 6582 while (n != c && offsetC < offsetB) { 6583 offsetC++; 6584 n = n.nextSibling; 6585 } 6586 6587 if (offsetC < offsetB) 6588 return -1; // before 6589 6590 return 1; // after 6591 } 6592 6593 // In the fourth case, none of three other cases hold: the containers of A and B 6594 // are siblings or descendants of sibling nodes. In this case, A is before B if 6595 // the container of A is before the container of B in a pre-order traversal of the 6596 // Ranges' context tree and A is after B otherwise. 6597 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6598 childA = containerA; 6599 6600 while (childA && childA.parentNode != cmnRoot) 6601 childA = childA.parentNode; 6602 6603 if (!childA) 6604 childA = cmnRoot; 6605 6606 childB = containerB; 6607 while (childB && childB.parentNode != cmnRoot) 6608 childB = childB.parentNode; 6609 6610 if (!childB) 6611 childB = cmnRoot; 6612 6613 if (childA == childB) 6614 return 0; // equal 6615 6616 n = cmnRoot.firstChild; 6617 while (n) { 6618 if (n == childA) 6619 return -1; // before 6620 6621 if (n == childB) 6622 return 1; // after 6623 6624 n = n.nextSibling; 6625 } 6626 }; 6627 6628 function _setEndPoint(st, n, o) { 6629 var ec, sc; 6630 6631 if (st) { 6632 t[START_CONTAINER] = n; 6633 t[START_OFFSET] = o; 6634 } else { 6635 t[END_CONTAINER] = n; 6636 t[END_OFFSET] = o; 6637 } 6638 6639 // If one boundary-point of a Range is set to have a root container 6640 // other than the current one for the Range, the Range is collapsed to 6641 // the new position. This enforces the restriction that both boundary- 6642 // points of a Range must have the same root container. 6643 ec = t[END_CONTAINER]; 6644 while (ec.parentNode) 6645 ec = ec.parentNode; 6646 6647 sc = t[START_CONTAINER]; 6648 while (sc.parentNode) 6649 sc = sc.parentNode; 6650 6651 if (sc == ec) { 6652 // The start position of a Range is guaranteed to never be after the 6653 // end position. To enforce this restriction, if the start is set to 6654 // be at a position after the end, the Range is collapsed to that 6655 // position. 6656 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6657 t.collapse(st); 6658 } else 6659 t.collapse(st); 6660 6661 t.collapsed = _isCollapsed(); 6662 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6663 }; 6664 6665 function _traverse(how) { 6666 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6667 6668 if (t[START_CONTAINER] == t[END_CONTAINER]) 6669 return _traverseSameContainer(how); 6670 6671 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6672 if (p == t[START_CONTAINER]) 6673 return _traverseCommonStartContainer(c, how); 6674 6675 ++endContainerDepth; 6676 } 6677 6678 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6679 if (p == t[END_CONTAINER]) 6680 return _traverseCommonEndContainer(c, how); 6681 6682 ++startContainerDepth; 6683 } 6684 6685 depthDiff = startContainerDepth - endContainerDepth; 6686 6687 startNode = t[START_CONTAINER]; 6688 while (depthDiff > 0) { 6689 startNode = startNode.parentNode; 6690 depthDiff--; 6691 } 6692 6693 endNode = t[END_CONTAINER]; 6694 while (depthDiff < 0) { 6695 endNode = endNode.parentNode; 6696 depthDiff++; 6697 } 6698 6699 // ascend the ancestor hierarchy until we have a common parent. 6700 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6701 startNode = sp; 6702 endNode = ep; 6703 } 6704 6705 return _traverseCommonAncestors(startNode, endNode, how); 6706 }; 6707 6708 function _traverseSameContainer(how) { 6709 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6710 6711 if (how != DELETE) 6712 frag = createDocumentFragment(); 6713 6714 // If selection is empty, just return the fragment 6715 if (t[START_OFFSET] == t[END_OFFSET]) 6716 return frag; 6717 6718 // Text node needs special case handling 6719 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6720 // get the substring 6721 s = t[START_CONTAINER].nodeValue; 6722 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6723 6724 // set the original text node to its new value 6725 if (how != CLONE) { 6726 n = t[START_CONTAINER]; 6727 start = t[START_OFFSET]; 6728 len = t[END_OFFSET] - t[START_OFFSET]; 6729 6730 if (start === 0 && len >= n.nodeValue.length - 1) { 6731 n.parentNode.removeChild(n); 6732 } else { 6733 n.deleteData(start, len); 6734 } 6735 6736 // Nothing is partially selected, so collapse to start point 6737 t.collapse(TRUE); 6738 } 6739 6740 if (how == DELETE) 6741 return; 6742 6743 if (sub.length > 0) { 6744 frag.appendChild(doc.createTextNode(sub)); 6745 } 6746 6747 return frag; 6748 } 6749 6750 // Copy nodes between the start/end offsets. 6751 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 6752 cnt = t[END_OFFSET] - t[START_OFFSET]; 6753 6754 while (n && cnt > 0) { 6755 sibling = n.nextSibling; 6756 xferNode = _traverseFullySelected(n, how); 6757 6758 if (frag) 6759 frag.appendChild( xferNode ); 6760 6761 --cnt; 6762 n = sibling; 6763 } 6764 6765 // Nothing is partially selected, so collapse to start point 6766 if (how != CLONE) 6767 t.collapse(TRUE); 6768 6769 return frag; 6770 }; 6771 6772 function _traverseCommonStartContainer(endAncestor, how) { 6773 var frag, n, endIdx, cnt, sibling, xferNode; 6774 6775 if (how != DELETE) 6776 frag = createDocumentFragment(); 6777 6778 n = _traverseRightBoundary(endAncestor, how); 6779 6780 if (frag) 6781 frag.appendChild(n); 6782 6783 endIdx = nodeIndex(endAncestor); 6784 cnt = endIdx - t[START_OFFSET]; 6785 6786 if (cnt <= 0) { 6787 // Collapse to just before the endAncestor, which 6788 // is partially selected. 6789 if (how != CLONE) { 6790 t.setEndBefore(endAncestor); 6791 t.collapse(FALSE); 6792 } 6793 6794 return frag; 6795 } 6796 6797 n = endAncestor.previousSibling; 6798 while (cnt > 0) { 6799 sibling = n.previousSibling; 6800 xferNode = _traverseFullySelected(n, how); 6801 6802 if (frag) 6803 frag.insertBefore(xferNode, frag.firstChild); 6804 6805 --cnt; 6806 n = sibling; 6807 } 6808 6809 // Collapse to just before the endAncestor, which 6810 // is partially selected. 6811 if (how != CLONE) { 6812 t.setEndBefore(endAncestor); 6813 t.collapse(FALSE); 6814 } 6815 6816 return frag; 6817 }; 6818 6819 function _traverseCommonEndContainer(startAncestor, how) { 6820 var frag, startIdx, n, cnt, sibling, xferNode; 6821 6822 if (how != DELETE) 6823 frag = createDocumentFragment(); 6824 6825 n = _traverseLeftBoundary(startAncestor, how); 6826 if (frag) 6827 frag.appendChild(n); 6828 6829 startIdx = nodeIndex(startAncestor); 6830 ++startIdx; // Because we already traversed it 6831 6832 cnt = t[END_OFFSET] - startIdx; 6833 n = startAncestor.nextSibling; 6834 while (n && cnt > 0) { 6835 sibling = n.nextSibling; 6836 xferNode = _traverseFullySelected(n, how); 6837 6838 if (frag) 6839 frag.appendChild(xferNode); 6840 6841 --cnt; 6842 n = sibling; 6843 } 6844 6845 if (how != CLONE) { 6846 t.setStartAfter(startAncestor); 6847 t.collapse(TRUE); 6848 } 6849 6850 return frag; 6851 }; 6852 6853 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 6854 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 6855 6856 if (how != DELETE) 6857 frag = createDocumentFragment(); 6858 6859 n = _traverseLeftBoundary(startAncestor, how); 6860 if (frag) 6861 frag.appendChild(n); 6862 6863 commonParent = startAncestor.parentNode; 6864 startOffset = nodeIndex(startAncestor); 6865 endOffset = nodeIndex(endAncestor); 6866 ++startOffset; 6867 6868 cnt = endOffset - startOffset; 6869 sibling = startAncestor.nextSibling; 6870 6871 while (cnt > 0) { 6872 nextSibling = sibling.nextSibling; 6873 n = _traverseFullySelected(sibling, how); 6874 6875 if (frag) 6876 frag.appendChild(n); 6877 6878 sibling = nextSibling; 6879 --cnt; 6880 } 6881 6882 n = _traverseRightBoundary(endAncestor, how); 6883 6884 if (frag) 6885 frag.appendChild(n); 6886 6887 if (how != CLONE) { 6888 t.setStartAfter(startAncestor); 6889 t.collapse(TRUE); 6890 } 6891 6892 return frag; 6893 }; 6894 6895 function _traverseRightBoundary(root, how) { 6896 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 6897 6898 if (next == root) 6899 return _traverseNode(next, isFullySelected, FALSE, how); 6900 6901 parent = next.parentNode; 6902 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 6903 6904 while (parent) { 6905 while (next) { 6906 prevSibling = next.previousSibling; 6907 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 6908 6909 if (how != DELETE) 6910 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 6911 6912 isFullySelected = TRUE; 6913 next = prevSibling; 6914 } 6915 6916 if (parent == root) 6917 return clonedParent; 6918 6919 next = parent.previousSibling; 6920 parent = parent.parentNode; 6921 6922 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 6923 6924 if (how != DELETE) 6925 clonedGrandParent.appendChild(clonedParent); 6926 6927 clonedParent = clonedGrandParent; 6928 } 6929 }; 6930 6931 function _traverseLeftBoundary(root, how) { 6932 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 6933 6934 if (next == root) 6935 return _traverseNode(next, isFullySelected, TRUE, how); 6936 6937 parent = next.parentNode; 6938 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 6939 6940 while (parent) { 6941 while (next) { 6942 nextSibling = next.nextSibling; 6943 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 6944 6945 if (how != DELETE) 6946 clonedParent.appendChild(clonedChild); 6947 6948 isFullySelected = TRUE; 6949 next = nextSibling; 6950 } 6951 6952 if (parent == root) 6953 return clonedParent; 6954 6955 next = parent.nextSibling; 6956 parent = parent.parentNode; 6957 6958 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 6959 6960 if (how != DELETE) 6961 clonedGrandParent.appendChild(clonedParent); 6962 6963 clonedParent = clonedGrandParent; 6964 } 6965 }; 6966 6967 function _traverseNode(n, isFullySelected, isLeft, how) { 6968 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 6969 6970 if (isFullySelected) 6971 return _traverseFullySelected(n, how); 6972 6973 if (n.nodeType == 3 /* TEXT_NODE */) { 6974 txtValue = n.nodeValue; 6975 6976 if (isLeft) { 6977 offset = t[START_OFFSET]; 6978 newNodeValue = txtValue.substring(offset); 6979 oldNodeValue = txtValue.substring(0, offset); 6980 } else { 6981 offset = t[END_OFFSET]; 6982 newNodeValue = txtValue.substring(0, offset); 6983 oldNodeValue = txtValue.substring(offset); 6984 } 6985 6986 if (how != CLONE) 6987 n.nodeValue = oldNodeValue; 6988 6989 if (how == DELETE) 6990 return; 6991 6992 newNode = dom.clone(n, FALSE); 6993 newNode.nodeValue = newNodeValue; 6994 6995 return newNode; 6996 } 6997 6998 if (how == DELETE) 6999 return; 7000 7001 return dom.clone(n, FALSE); 7002 }; 7003 7004 function _traverseFullySelected(n, how) { 7005 if (how != DELETE) 7006 return how == CLONE ? dom.clone(n, TRUE) : n; 7007 7008 n.parentNode.removeChild(n); 7009 }; 7010 7011 function toStringIE() { 7012 return dom.create('body', null, cloneContents()).outerText; 7013 } 7014 7015 return t; 7016 }; 7017 7018 ns.Range = Range; 7019 7020 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7021 Range.prototype.toString = function() { 7022 return this.toStringIE(); 7023 }; 7024 })(tinymce.dom); 7025 7026 (function() { 7027 function Selection(selection) { 7028 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7029 7030 function getPosition(rng, start) { 7031 var checkRng, startIndex = 0, endIndex, inside, 7032 children, child, offset, index, position = -1, parent; 7033 7034 // Setup test range, collapse it and get the parent 7035 checkRng = rng.duplicate(); 7036 checkRng.collapse(start); 7037 parent = checkRng.parentElement(); 7038 7039 // Check if the selection is within the right document 7040 if (parent.ownerDocument !== selection.dom.doc) 7041 return; 7042 7043 // IE will report non editable elements as it's parent so look for an editable one 7044 while (parent.contentEditable === "false") { 7045 parent = parent.parentNode; 7046 } 7047 7048 // If parent doesn't have any children then return that we are inside the element 7049 if (!parent.hasChildNodes()) { 7050 return {node : parent, inside : 1}; 7051 } 7052 7053 // Setup node list and endIndex 7054 children = parent.children; 7055 endIndex = children.length - 1; 7056 7057 // Perform a binary search for the position 7058 while (startIndex <= endIndex) { 7059 index = Math.floor((startIndex + endIndex) / 2); 7060 7061 // Move selection to node and compare the ranges 7062 child = children[index]; 7063 checkRng.moveToElementText(child); 7064 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7065 7066 // Before/after or an exact match 7067 if (position > 0) { 7068 endIndex = index - 1; 7069 } else if (position < 0) { 7070 startIndex = index + 1; 7071 } else { 7072 return {node : child}; 7073 } 7074 } 7075 7076 // Check if child position is before or we didn't find a position 7077 if (position < 0) { 7078 // No element child was found use the parent element and the offset inside that 7079 if (!child) { 7080 checkRng.moveToElementText(parent); 7081 checkRng.collapse(true); 7082 child = parent; 7083 inside = true; 7084 } else 7085 checkRng.collapse(false); 7086 7087 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7088 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7089 offset = 0; 7090 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7091 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7092 break; 7093 } 7094 7095 offset++; 7096 } 7097 } else { 7098 // Child position is after the selection endpoint 7099 checkRng.collapse(true); 7100 7101 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7102 offset = 0; 7103 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7104 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7105 break; 7106 } 7107 7108 offset++; 7109 } 7110 } 7111 7112 return {node : child, position : position, offset : offset, inside : inside}; 7113 }; 7114 7115 // Returns a W3C DOM compatible range object by using the IE Range API 7116 function getRange() { 7117 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7118 7119 // If selection is outside the current document just return an empty range 7120 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7121 if (element.ownerDocument != dom.doc) 7122 return domRange; 7123 7124 collapsed = selection.isCollapsed(); 7125 7126 // Handle control selection 7127 if (ieRange.item) { 7128 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7129 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7130 7131 return domRange; 7132 } 7133 7134 function findEndPoint(start) { 7135 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7136 7137 container = endPoint.node; 7138 offset = endPoint.offset; 7139 7140 if (endPoint.inside && !container.hasChildNodes()) { 7141 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7142 return; 7143 } 7144 7145 if (offset === undef) { 7146 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7147 return; 7148 } 7149 7150 if (endPoint.position < 0) { 7151 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7152 7153 if (!sibling) { 7154 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7155 return; 7156 } 7157 7158 if (!offset) { 7159 if (sibling.nodeType == 3) 7160 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7161 else 7162 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7163 7164 return; 7165 } 7166 7167 // Find the text node and offset 7168 while (sibling) { 7169 nodeValue = sibling.nodeValue; 7170 textNodeOffset += nodeValue.length; 7171 7172 // We are at or passed the position we where looking for 7173 if (textNodeOffset >= offset) { 7174 container = sibling; 7175 textNodeOffset -= offset; 7176 textNodeOffset = nodeValue.length - textNodeOffset; 7177 break; 7178 } 7179 7180 sibling = sibling.nextSibling; 7181 } 7182 } else { 7183 // Find the text node and offset 7184 sibling = container.previousSibling; 7185 7186 if (!sibling) 7187 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7188 7189 // If there isn't any text to loop then use the first position 7190 if (!offset) { 7191 if (container.nodeType == 3) 7192 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7193 else 7194 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7195 7196 return; 7197 } 7198 7199 while (sibling) { 7200 textNodeOffset += sibling.nodeValue.length; 7201 7202 // We are at or passed the position we where looking for 7203 if (textNodeOffset >= offset) { 7204 container = sibling; 7205 textNodeOffset -= offset; 7206 break; 7207 } 7208 7209 sibling = sibling.previousSibling; 7210 } 7211 } 7212 7213 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7214 }; 7215 7216 try { 7217 // Find start point 7218 findEndPoint(true); 7219 7220 // Find end point if needed 7221 if (!collapsed) 7222 findEndPoint(); 7223 } catch (ex) { 7224 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7225 // access the nodeValue or other properties of text nodes. This seems to happend when 7226 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7227 if (ex.number == -2147024809) { 7228 // Get the current selection 7229 bookmark = self.getBookmark(2); 7230 7231 // Get start element 7232 tmpRange = ieRange.duplicate(); 7233 tmpRange.collapse(true); 7234 element = tmpRange.parentElement(); 7235 7236 // Get end element 7237 if (!collapsed) { 7238 tmpRange = ieRange.duplicate(); 7239 tmpRange.collapse(false); 7240 element2 = tmpRange.parentElement(); 7241 element2.innerHTML = element2.innerHTML; 7242 } 7243 7244 // Remove the broken elements 7245 element.innerHTML = element.innerHTML; 7246 7247 // Restore the selection 7248 self.moveToBookmark(bookmark); 7249 7250 // Since the range has moved we need to re-get it 7251 ieRange = selection.getRng(); 7252 7253 // Find start point 7254 findEndPoint(true); 7255 7256 // Find end point if needed 7257 if (!collapsed) 7258 findEndPoint(); 7259 } else 7260 throw ex; // Throw other errors 7261 } 7262 7263 return domRange; 7264 }; 7265 7266 this.getBookmark = function(type) { 7267 var rng = selection.getRng(), start, end, bookmark = {}; 7268 7269 function getIndexes(node) { 7270 var parent, root, children, i, indexes = []; 7271 7272 parent = node.parentNode; 7273 root = dom.getRoot().parentNode; 7274 7275 while (parent != root && parent.nodeType !== 9) { 7276 children = parent.children; 7277 7278 i = children.length; 7279 while (i--) { 7280 if (node === children[i]) { 7281 indexes.push(i); 7282 break; 7283 } 7284 } 7285 7286 node = parent; 7287 parent = parent.parentNode; 7288 } 7289 7290 return indexes; 7291 }; 7292 7293 function getBookmarkEndPoint(start) { 7294 var position; 7295 7296 position = getPosition(rng, start); 7297 if (position) { 7298 return { 7299 position : position.position, 7300 offset : position.offset, 7301 indexes : getIndexes(position.node), 7302 inside : position.inside 7303 }; 7304 } 7305 }; 7306 7307 // Non ubstructive bookmark 7308 if (type === 2) { 7309 // Handle text selection 7310 if (!rng.item) { 7311 bookmark.start = getBookmarkEndPoint(true); 7312 7313 if (!selection.isCollapsed()) 7314 bookmark.end = getBookmarkEndPoint(); 7315 } else 7316 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7317 } 7318 7319 return bookmark; 7320 }; 7321 7322 this.moveToBookmark = function(bookmark) { 7323 var rng, body = dom.doc.body; 7324 7325 function resolveIndexes(indexes) { 7326 var node, i, idx, children; 7327 7328 node = dom.getRoot(); 7329 for (i = indexes.length - 1; i >= 0; i--) { 7330 children = node.children; 7331 idx = indexes[i]; 7332 7333 if (idx <= children.length - 1) { 7334 node = children[idx]; 7335 } 7336 } 7337 7338 return node; 7339 }; 7340 7341 function setBookmarkEndPoint(start) { 7342 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7343 7344 if (endPoint) { 7345 moveLeft = endPoint.position > 0; 7346 7347 moveRng = body.createTextRange(); 7348 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7349 7350 offset = endPoint.offset; 7351 if (offset !== undef) { 7352 moveRng.collapse(endPoint.inside || moveLeft); 7353 moveRng.moveStart('character', moveLeft ? -offset : offset); 7354 } else 7355 moveRng.collapse(start); 7356 7357 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7358 7359 if (start) 7360 rng.collapse(true); 7361 } 7362 }; 7363 7364 if (bookmark.start) { 7365 if (bookmark.start.ctrl) { 7366 rng = body.createControlRange(); 7367 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7368 rng.select(); 7369 } else { 7370 rng = body.createTextRange(); 7371 setBookmarkEndPoint(true); 7372 setBookmarkEndPoint(); 7373 rng.select(); 7374 } 7375 } 7376 }; 7377 7378 this.addRange = function(rng) { 7379 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body; 7380 7381 function setEndPoint(start) { 7382 var container, offset, marker, tmpRng, nodes; 7383 7384 marker = dom.create('a'); 7385 container = start ? startContainer : endContainer; 7386 offset = start ? startOffset : endOffset; 7387 tmpRng = ieRng.duplicate(); 7388 7389 if (container == doc || container == doc.documentElement) { 7390 container = body; 7391 offset = 0; 7392 } 7393 7394 if (container.nodeType == 3) { 7395 container.parentNode.insertBefore(marker, container); 7396 tmpRng.moveToElementText(marker); 7397 tmpRng.moveStart('character', offset); 7398 dom.remove(marker); 7399 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7400 } else { 7401 nodes = container.childNodes; 7402 7403 if (nodes.length) { 7404 if (offset >= nodes.length) { 7405 dom.insertAfter(marker, nodes[nodes.length - 1]); 7406 } else { 7407 container.insertBefore(marker, nodes[offset]); 7408 } 7409 7410 tmpRng.moveToElementText(marker); 7411 } else if (container.canHaveHTML) { 7412 // Empty node selection for example <div>|</div> 7413 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7414 container.innerHTML = '<span>\uFEFF</span>'; 7415 marker = container.firstChild; 7416 tmpRng.moveToElementText(marker); 7417 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7418 } 7419 7420 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7421 dom.remove(marker); 7422 } 7423 } 7424 7425 // Setup some shorter versions 7426 startContainer = rng.startContainer; 7427 startOffset = rng.startOffset; 7428 endContainer = rng.endContainer; 7429 endOffset = rng.endOffset; 7430 ieRng = body.createTextRange(); 7431 7432 // If single element selection then try making a control selection out of it 7433 if (startContainer == endContainer && startContainer.nodeType == 1) { 7434 // Trick to place the caret inside an empty block element like <p></p> 7435 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7436 if (startContainer.canHaveHTML) { 7437 // Check if previous sibling is an empty block if it is then we need to render it 7438 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7439 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7440 sibling = startContainer.previousSibling; 7441 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7442 sibling.innerHTML = '\uFEFF'; 7443 } else { 7444 sibling = null; 7445 } 7446 7447 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7448 ieRng.moveToElementText(startContainer.lastChild); 7449 ieRng.select(); 7450 dom.doc.selection.clear(); 7451 startContainer.innerHTML = ''; 7452 7453 if (sibling) { 7454 sibling.innerHTML = ''; 7455 } 7456 return; 7457 } else { 7458 startOffset = dom.nodeIndex(startContainer); 7459 startContainer = startContainer.parentNode; 7460 } 7461 } 7462 7463 if (startOffset == endOffset - 1) { 7464 try { 7465 ctrlRng = body.createControlRange(); 7466 ctrlRng.addElement(startContainer.childNodes[startOffset]); 7467 ctrlRng.select(); 7468 return; 7469 } catch (ex) { 7470 // Ignore 7471 } 7472 } 7473 } 7474 7475 // Set start/end point of selection 7476 setEndPoint(true); 7477 setEndPoint(); 7478 7479 // Select the new range and scroll it into view 7480 ieRng.select(); 7481 }; 7482 7483 // Expose range method 7484 this.getRangeAt = getRange; 7485 }; 7486 7487 // Expose the selection object 7488 tinymce.dom.TridentSelection = Selection; 7489 })(); 7490 7491 7492 /* 7493 * Sizzle CSS Selector Engine 7494 * Copyright, The Dojo Foundation 7495 * Released under the MIT, BSD, and GPL Licenses. 7496 * More information: http://sizzlejs.com/ 7497 */ 7498 (function(){ 7499 7500 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7501 expando = "sizcache", 7502 done = 0, 7503 toString = Object.prototype.toString, 7504 hasDuplicate = false, 7505 baseHasDuplicate = true, 7506 rBackslash = /\\/g, 7507 rReturn = /\r\n/g, 7508 rNonWord = /\W/; 7509 7510 // Here we check if the JavaScript engine is using some sort of 7511 // optimization where it does not always call our comparision 7512 // function. If that is the case, discard the hasDuplicate value. 7513 // Thus far that includes Google Chrome. 7514 [0, 0].sort(function() { 7515 baseHasDuplicate = false; 7516 return 0; 7517 }); 7518 7519 var Sizzle = function( selector, context, results, seed ) { 7520 results = results || []; 7521 context = context || document; 7522 7523 var origContext = context; 7524 7525 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7526 return []; 7527 } 7528 7529 if ( !selector || typeof selector !== "string" ) { 7530 return results; 7531 } 7532 7533 var m, set, checkSet, extra, ret, cur, pop, i, 7534 prune = true, 7535 contextXML = Sizzle.isXML( context ), 7536 parts = [], 7537 soFar = selector; 7538 7539 // Reset the position of the chunker regexp (start from head) 7540 do { 7541 chunker.exec( "" ); 7542 m = chunker.exec( soFar ); 7543 7544 if ( m ) { 7545 soFar = m[3]; 7546 7547 parts.push( m[1] ); 7548 7549 if ( m[2] ) { 7550 extra = m[3]; 7551 break; 7552 } 7553 } 7554 } while ( m ); 7555 7556 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7557 7558 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7559 set = posProcess( parts[0] + parts[1], context, seed ); 7560 7561 } else { 7562 set = Expr.relative[ parts[0] ] ? 7563 [ context ] : 7564 Sizzle( parts.shift(), context ); 7565 7566 while ( parts.length ) { 7567 selector = parts.shift(); 7568 7569 if ( Expr.relative[ selector ] ) { 7570 selector += parts.shift(); 7571 } 7572 7573 set = posProcess( selector, set, seed ); 7574 } 7575 } 7576 7577 } else { 7578 // Take a shortcut and set the context if the root selector is an ID 7579 // (but not if it'll be faster if the inner selector is an ID) 7580 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7581 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7582 7583 ret = Sizzle.find( parts.shift(), context, contextXML ); 7584 context = ret.expr ? 7585 Sizzle.filter( ret.expr, ret.set )[0] : 7586 ret.set[0]; 7587 } 7588 7589 if ( context ) { 7590 ret = seed ? 7591 { expr: parts.pop(), set: makeArray(seed) } : 7592 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7593 7594 set = ret.expr ? 7595 Sizzle.filter( ret.expr, ret.set ) : 7596 ret.set; 7597 7598 if ( parts.length > 0 ) { 7599 checkSet = makeArray( set ); 7600 7601 } else { 7602 prune = false; 7603 } 7604 7605 while ( parts.length ) { 7606 cur = parts.pop(); 7607 pop = cur; 7608 7609 if ( !Expr.relative[ cur ] ) { 7610 cur = ""; 7611 } else { 7612 pop = parts.pop(); 7613 } 7614 7615 if ( pop == null ) { 7616 pop = context; 7617 } 7618 7619 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7620 } 7621 7622 } else { 7623 checkSet = parts = []; 7624 } 7625 } 7626 7627 if ( !checkSet ) { 7628 checkSet = set; 7629 } 7630 7631 if ( !checkSet ) { 7632 Sizzle.error( cur || selector ); 7633 } 7634 7635 if ( toString.call(checkSet) === "[object Array]" ) { 7636 if ( !prune ) { 7637 results.push.apply( results, checkSet ); 7638 7639 } else if ( context && context.nodeType === 1 ) { 7640 for ( i = 0; checkSet[i] != null; i++ ) { 7641 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7642 results.push( set[i] ); 7643 } 7644 } 7645 7646 } else { 7647 for ( i = 0; checkSet[i] != null; i++ ) { 7648 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7649 results.push( set[i] ); 7650 } 7651 } 7652 } 7653 7654 } else { 7655 makeArray( checkSet, results ); 7656 } 7657 7658 if ( extra ) { 7659 Sizzle( extra, origContext, results, seed ); 7660 Sizzle.uniqueSort( results ); 7661 } 7662 7663 return results; 7664 }; 7665 7666 Sizzle.uniqueSort = function( results ) { 7667 if ( sortOrder ) { 7668 hasDuplicate = baseHasDuplicate; 7669 results.sort( sortOrder ); 7670 7671 if ( hasDuplicate ) { 7672 for ( var i = 1; i < results.length; i++ ) { 7673 if ( results[i] === results[ i - 1 ] ) { 7674 results.splice( i--, 1 ); 7675 } 7676 } 7677 } 7678 } 7679 7680 return results; 7681 }; 7682 7683 Sizzle.matches = function( expr, set ) { 7684 return Sizzle( expr, null, null, set ); 7685 }; 7686 7687 Sizzle.matchesSelector = function( node, expr ) { 7688 return Sizzle( expr, null, null, [node] ).length > 0; 7689 }; 7690 7691 Sizzle.find = function( expr, context, isXML ) { 7692 var set, i, len, match, type, left; 7693 7694 if ( !expr ) { 7695 return []; 7696 } 7697 7698 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7699 type = Expr.order[i]; 7700 7701 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7702 left = match[1]; 7703 match.splice( 1, 1 ); 7704 7705 if ( left.substr( left.length - 1 ) !== "\\" ) { 7706 match[1] = (match[1] || "").replace( rBackslash, "" ); 7707 set = Expr.find[ type ]( match, context, isXML ); 7708 7709 if ( set != null ) { 7710 expr = expr.replace( Expr.match[ type ], "" ); 7711 break; 7712 } 7713 } 7714 } 7715 } 7716 7717 if ( !set ) { 7718 set = typeof context.getElementsByTagName !== "undefined" ? 7719 context.getElementsByTagName( "*" ) : 7720 []; 7721 } 7722 7723 return { set: set, expr: expr }; 7724 }; 7725 7726 Sizzle.filter = function( expr, set, inplace, not ) { 7727 var match, anyFound, 7728 type, found, item, filter, left, 7729 i, pass, 7730 old = expr, 7731 result = [], 7732 curLoop = set, 7733 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 7734 7735 while ( expr && set.length ) { 7736 for ( type in Expr.filter ) { 7737 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 7738 filter = Expr.filter[ type ]; 7739 left = match[1]; 7740 7741 anyFound = false; 7742 7743 match.splice(1,1); 7744 7745 if ( left.substr( left.length - 1 ) === "\\" ) { 7746 continue; 7747 } 7748 7749 if ( curLoop === result ) { 7750 result = []; 7751 } 7752 7753 if ( Expr.preFilter[ type ] ) { 7754 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 7755 7756 if ( !match ) { 7757 anyFound = found = true; 7758 7759 } else if ( match === true ) { 7760 continue; 7761 } 7762 } 7763 7764 if ( match ) { 7765 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 7766 if ( item ) { 7767 found = filter( item, match, i, curLoop ); 7768 pass = not ^ found; 7769 7770 if ( inplace && found != null ) { 7771 if ( pass ) { 7772 anyFound = true; 7773 7774 } else { 7775 curLoop[i] = false; 7776 } 7777 7778 } else if ( pass ) { 7779 result.push( item ); 7780 anyFound = true; 7781 } 7782 } 7783 } 7784 } 7785 7786 if ( found !== undefined ) { 7787 if ( !inplace ) { 7788 curLoop = result; 7789 } 7790 7791 expr = expr.replace( Expr.match[ type ], "" ); 7792 7793 if ( !anyFound ) { 7794 return []; 7795 } 7796 7797 break; 7798 } 7799 } 7800 } 7801 7802 // Improper expression 7803 if ( expr === old ) { 7804 if ( anyFound == null ) { 7805 Sizzle.error( expr ); 7806 7807 } else { 7808 break; 7809 } 7810 } 7811 7812 old = expr; 7813 } 7814 7815 return curLoop; 7816 }; 7817 7818 Sizzle.error = function( msg ) { 7819 throw new Error( "Syntax error, unrecognized expression: " + msg ); 7820 }; 7821 7822 var getText = Sizzle.getText = function( elem ) { 7823 var i, node, 7824 nodeType = elem.nodeType, 7825 ret = ""; 7826 7827 if ( nodeType ) { 7828 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 7829 // Use textContent || innerText for elements 7830 if ( typeof elem.textContent === 'string' ) { 7831 return elem.textContent; 7832 } else if ( typeof elem.innerText === 'string' ) { 7833 // Replace IE's carriage returns 7834 return elem.innerText.replace( rReturn, '' ); 7835 } else { 7836 // Traverse it's children 7837 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 7838 ret += getText( elem ); 7839 } 7840 } 7841 } else if ( nodeType === 3 || nodeType === 4 ) { 7842 return elem.nodeValue; 7843 } 7844 } else { 7845 7846 // If no nodeType, this is expected to be an array 7847 for ( i = 0; (node = elem[i]); i++ ) { 7848 // Do not traverse comment nodes 7849 if ( node.nodeType !== 8 ) { 7850 ret += getText( node ); 7851 } 7852 } 7853 } 7854 return ret; 7855 }; 7856 7857 var Expr = Sizzle.selectors = { 7858 order: [ "ID", "NAME", "TAG" ], 7859 7860 match: { 7861 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7862 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7863 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 7864 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 7865 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 7866 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 7867 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 7868 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 7869 }, 7870 7871 leftMatch: {}, 7872 7873 attrMap: { 7874 "class": "className", 7875 "for": "htmlFor" 7876 }, 7877 7878 attrHandle: { 7879 href: function( elem ) { 7880 return elem.getAttribute( "href" ); 7881 }, 7882 type: function( elem ) { 7883 return elem.getAttribute( "type" ); 7884 } 7885 }, 7886 7887 relative: { 7888 "+": function(checkSet, part){ 7889 var isPartStr = typeof part === "string", 7890 isTag = isPartStr && !rNonWord.test( part ), 7891 isPartStrNotTag = isPartStr && !isTag; 7892 7893 if ( isTag ) { 7894 part = part.toLowerCase(); 7895 } 7896 7897 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 7898 if ( (elem = checkSet[i]) ) { 7899 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 7900 7901 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 7902 elem || false : 7903 elem === part; 7904 } 7905 } 7906 7907 if ( isPartStrNotTag ) { 7908 Sizzle.filter( part, checkSet, true ); 7909 } 7910 }, 7911 7912 ">": function( checkSet, part ) { 7913 var elem, 7914 isPartStr = typeof part === "string", 7915 i = 0, 7916 l = checkSet.length; 7917 7918 if ( isPartStr && !rNonWord.test( part ) ) { 7919 part = part.toLowerCase(); 7920 7921 for ( ; i < l; i++ ) { 7922 elem = checkSet[i]; 7923 7924 if ( elem ) { 7925 var parent = elem.parentNode; 7926 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 7927 } 7928 } 7929 7930 } else { 7931 for ( ; i < l; i++ ) { 7932 elem = checkSet[i]; 7933 7934 if ( elem ) { 7935 checkSet[i] = isPartStr ? 7936 elem.parentNode : 7937 elem.parentNode === part; 7938 } 7939 } 7940 7941 if ( isPartStr ) { 7942 Sizzle.filter( part, checkSet, true ); 7943 } 7944 } 7945 }, 7946 7947 "": function(checkSet, part, isXML){ 7948 var nodeCheck, 7949 doneName = done++, 7950 checkFn = dirCheck; 7951 7952 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7953 part = part.toLowerCase(); 7954 nodeCheck = part; 7955 checkFn = dirNodeCheck; 7956 } 7957 7958 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 7959 }, 7960 7961 "~": function( checkSet, part, isXML ) { 7962 var nodeCheck, 7963 doneName = done++, 7964 checkFn = dirCheck; 7965 7966 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7967 part = part.toLowerCase(); 7968 nodeCheck = part; 7969 checkFn = dirNodeCheck; 7970 } 7971 7972 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 7973 } 7974 }, 7975 7976 find: { 7977 ID: function( match, context, isXML ) { 7978 if ( typeof context.getElementById !== "undefined" && !isXML ) { 7979 var m = context.getElementById(match[1]); 7980 // Check parentNode to catch when Blackberry 4.6 returns 7981 // nodes that are no longer in the document #6963 7982 return m && m.parentNode ? [m] : []; 7983 } 7984 }, 7985 7986 NAME: function( match, context ) { 7987 if ( typeof context.getElementsByName !== "undefined" ) { 7988 var ret = [], 7989 results = context.getElementsByName( match[1] ); 7990 7991 for ( var i = 0, l = results.length; i < l; i++ ) { 7992 if ( results[i].getAttribute("name") === match[1] ) { 7993 ret.push( results[i] ); 7994 } 7995 } 7996 7997 return ret.length === 0 ? null : ret; 7998 } 7999 }, 8000 8001 TAG: function( match, context ) { 8002 if ( typeof context.getElementsByTagName !== "undefined" ) { 8003 return context.getElementsByTagName( match[1] ); 8004 } 8005 } 8006 }, 8007 preFilter: { 8008 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 8009 match = " " + match[1].replace( rBackslash, "" ) + " "; 8010 8011 if ( isXML ) { 8012 return match; 8013 } 8014 8015 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8016 if ( elem ) { 8017 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8018 if ( !inplace ) { 8019 result.push( elem ); 8020 } 8021 8022 } else if ( inplace ) { 8023 curLoop[i] = false; 8024 } 8025 } 8026 } 8027 8028 return false; 8029 }, 8030 8031 ID: function( match ) { 8032 return match[1].replace( rBackslash, "" ); 8033 }, 8034 8035 TAG: function( match, curLoop ) { 8036 return match[1].replace( rBackslash, "" ).toLowerCase(); 8037 }, 8038 8039 CHILD: function( match ) { 8040 if ( match[1] === "nth" ) { 8041 if ( !match[2] ) { 8042 Sizzle.error( match[0] ); 8043 } 8044 8045 match[2] = match[2].replace(/^\+|\s*/g, ''); 8046 8047 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8048 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8049 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8050 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8051 8052 // calculate the numbers (first)n+(last) including if they are negative 8053 match[2] = (test[1] + (test[2] || 1)) - 0; 8054 match[3] = test[3] - 0; 8055 } 8056 else if ( match[2] ) { 8057 Sizzle.error( match[0] ); 8058 } 8059 8060 // TODO: Move to normal caching system 8061 match[0] = done++; 8062 8063 return match; 8064 }, 8065 8066 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8067 var name = match[1] = match[1].replace( rBackslash, "" ); 8068 8069 if ( !isXML && Expr.attrMap[name] ) { 8070 match[1] = Expr.attrMap[name]; 8071 } 8072 8073 // Handle if an un-quoted value was used 8074 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8075 8076 if ( match[2] === "~=" ) { 8077 match[4] = " " + match[4] + " "; 8078 } 8079 8080 return match; 8081 }, 8082 8083 PSEUDO: function( match, curLoop, inplace, result, not ) { 8084 if ( match[1] === "not" ) { 8085 // If we're dealing with a complex expression, or a simple one 8086 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8087 match[3] = Sizzle(match[3], null, null, curLoop); 8088 8089 } else { 8090 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8091 8092 if ( !inplace ) { 8093 result.push.apply( result, ret ); 8094 } 8095 8096 return false; 8097 } 8098 8099 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8100 return true; 8101 } 8102 8103 return match; 8104 }, 8105 8106 POS: function( match ) { 8107 match.unshift( true ); 8108 8109 return match; 8110 } 8111 }, 8112 8113 filters: { 8114 enabled: function( elem ) { 8115 return elem.disabled === false && elem.type !== "hidden"; 8116 }, 8117 8118 disabled: function( elem ) { 8119 return elem.disabled === true; 8120 }, 8121 8122 checked: function( elem ) { 8123 return elem.checked === true; 8124 }, 8125 8126 selected: function( elem ) { 8127 // Accessing this property makes selected-by-default 8128 // options in Safari work properly 8129 if ( elem.parentNode ) { 8130 elem.parentNode.selectedIndex; 8131 } 8132 8133 return elem.selected === true; 8134 }, 8135 8136 parent: function( elem ) { 8137 return !!elem.firstChild; 8138 }, 8139 8140 empty: function( elem ) { 8141 return !elem.firstChild; 8142 }, 8143 8144 has: function( elem, i, match ) { 8145 return !!Sizzle( match[3], elem ).length; 8146 }, 8147 8148 header: function( elem ) { 8149 return (/h\d/i).test( elem.nodeName ); 8150 }, 8151 8152 text: function( elem ) { 8153 var attr = elem.getAttribute( "type" ), type = elem.type; 8154 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8155 // use getAttribute instead to test this case 8156 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8157 }, 8158 8159 radio: function( elem ) { 8160 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8161 }, 8162 8163 checkbox: function( elem ) { 8164 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8165 }, 8166 8167 file: function( elem ) { 8168 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8169 }, 8170 8171 password: function( elem ) { 8172 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8173 }, 8174 8175 submit: function( elem ) { 8176 var name = elem.nodeName.toLowerCase(); 8177 return (name === "input" || name === "button") && "submit" === elem.type; 8178 }, 8179 8180 image: function( elem ) { 8181 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8182 }, 8183 8184 reset: function( elem ) { 8185 var name = elem.nodeName.toLowerCase(); 8186 return (name === "input" || name === "button") && "reset" === elem.type; 8187 }, 8188 8189 button: function( elem ) { 8190 var name = elem.nodeName.toLowerCase(); 8191 return name === "input" && "button" === elem.type || name === "button"; 8192 }, 8193 8194 input: function( elem ) { 8195 return (/input|select|textarea|button/i).test( elem.nodeName ); 8196 }, 8197 8198 focus: function( elem ) { 8199 return elem === elem.ownerDocument.activeElement; 8200 } 8201 }, 8202 setFilters: { 8203 first: function( elem, i ) { 8204 return i === 0; 8205 }, 8206 8207 last: function( elem, i, match, array ) { 8208 return i === array.length - 1; 8209 }, 8210 8211 even: function( elem, i ) { 8212 return i % 2 === 0; 8213 }, 8214 8215 odd: function( elem, i ) { 8216 return i % 2 === 1; 8217 }, 8218 8219 lt: function( elem, i, match ) { 8220 return i < match[3] - 0; 8221 }, 8222 8223 gt: function( elem, i, match ) { 8224 return i > match[3] - 0; 8225 }, 8226 8227 nth: function( elem, i, match ) { 8228 return match[3] - 0 === i; 8229 }, 8230 8231 eq: function( elem, i, match ) { 8232 return match[3] - 0 === i; 8233 } 8234 }, 8235 filter: { 8236 PSEUDO: function( elem, match, i, array ) { 8237 var name = match[1], 8238 filter = Expr.filters[ name ]; 8239 8240 if ( filter ) { 8241 return filter( elem, i, match, array ); 8242 8243 } else if ( name === "contains" ) { 8244 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8245 8246 } else if ( name === "not" ) { 8247 var not = match[3]; 8248 8249 for ( var j = 0, l = not.length; j < l; j++ ) { 8250 if ( not[j] === elem ) { 8251 return false; 8252 } 8253 } 8254 8255 return true; 8256 8257 } else { 8258 Sizzle.error( name ); 8259 } 8260 }, 8261 8262 CHILD: function( elem, match ) { 8263 var first, last, 8264 doneName, parent, cache, 8265 count, diff, 8266 type = match[1], 8267 node = elem; 8268 8269 switch ( type ) { 8270 case "only": 8271 case "first": 8272 while ( (node = node.previousSibling) ) { 8273 if ( node.nodeType === 1 ) { 8274 return false; 8275 } 8276 } 8277 8278 if ( type === "first" ) { 8279 return true; 8280 } 8281 8282 node = elem; 8283 8284 /* falls through */ 8285 case "last": 8286 while ( (node = node.nextSibling) ) { 8287 if ( node.nodeType === 1 ) { 8288 return false; 8289 } 8290 } 8291 8292 return true; 8293 8294 case "nth": 8295 first = match[2]; 8296 last = match[3]; 8297 8298 if ( first === 1 && last === 0 ) { 8299 return true; 8300 } 8301 8302 doneName = match[0]; 8303 parent = elem.parentNode; 8304 8305 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8306 count = 0; 8307 8308 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8309 if ( node.nodeType === 1 ) { 8310 node.nodeIndex = ++count; 8311 } 8312 } 8313 8314 parent[ expando ] = doneName; 8315 } 8316 8317 diff = elem.nodeIndex - last; 8318 8319 if ( first === 0 ) { 8320 return diff === 0; 8321 8322 } else { 8323 return ( diff % first === 0 && diff / first >= 0 ); 8324 } 8325 } 8326 }, 8327 8328 ID: function( elem, match ) { 8329 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8330 }, 8331 8332 TAG: function( elem, match ) { 8333 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8334 }, 8335 8336 CLASS: function( elem, match ) { 8337 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8338 .indexOf( match ) > -1; 8339 }, 8340 8341 ATTR: function( elem, match ) { 8342 var name = match[1], 8343 result = Sizzle.attr ? 8344 Sizzle.attr( elem, name ) : 8345 Expr.attrHandle[ name ] ? 8346 Expr.attrHandle[ name ]( elem ) : 8347 elem[ name ] != null ? 8348 elem[ name ] : 8349 elem.getAttribute( name ), 8350 value = result + "", 8351 type = match[2], 8352 check = match[4]; 8353 8354 return result == null ? 8355 type === "!=" : 8356 !type && Sizzle.attr ? 8357 result != null : 8358 type === "=" ? 8359 value === check : 8360 type === "*=" ? 8361 value.indexOf(check) >= 0 : 8362 type === "~=" ? 8363 (" " + value + " ").indexOf(check) >= 0 : 8364 !check ? 8365 value && result !== false : 8366 type === "!=" ? 8367 value !== check : 8368 type === "^=" ? 8369 value.indexOf(check) === 0 : 8370 type === "$=" ? 8371 value.substr(value.length - check.length) === check : 8372 type === "|=" ? 8373 value === check || value.substr(0, check.length + 1) === check + "-" : 8374 false; 8375 }, 8376 8377 POS: function( elem, match, i, array ) { 8378 var name = match[2], 8379 filter = Expr.setFilters[ name ]; 8380 8381 if ( filter ) { 8382 return filter( elem, i, match, array ); 8383 } 8384 } 8385 } 8386 }; 8387 8388 var origPOS = Expr.match.POS, 8389 fescape = function(all, num){ 8390 return "\\" + (num - 0 + 1); 8391 }; 8392 8393 for ( var type in Expr.match ) { 8394 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8395 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8396 } 8397 // Expose origPOS 8398 // "global" as in regardless of relation to brackets/parens 8399 Expr.match.globalPOS = origPOS; 8400 8401 var makeArray = function( array, results ) { 8402 array = Array.prototype.slice.call( array, 0 ); 8403 8404 if ( results ) { 8405 results.push.apply( results, array ); 8406 return results; 8407 } 8408 8409 return array; 8410 }; 8411 8412 // Perform a simple check to determine if the browser is capable of 8413 // converting a NodeList to an array using builtin methods. 8414 // Also verifies that the returned array holds DOM nodes 8415 // (which is not the case in the Blackberry browser) 8416 try { 8417 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8418 8419 // Provide a fallback method if it does not work 8420 } catch( e ) { 8421 makeArray = function( array, results ) { 8422 var i = 0, 8423 ret = results || []; 8424 8425 if ( toString.call(array) === "[object Array]" ) { 8426 Array.prototype.push.apply( ret, array ); 8427 8428 } else { 8429 if ( typeof array.length === "number" ) { 8430 for ( var l = array.length; i < l; i++ ) { 8431 ret.push( array[i] ); 8432 } 8433 8434 } else { 8435 for ( ; array[i]; i++ ) { 8436 ret.push( array[i] ); 8437 } 8438 } 8439 } 8440 8441 return ret; 8442 }; 8443 } 8444 8445 var sortOrder, siblingCheck; 8446 8447 if ( document.documentElement.compareDocumentPosition ) { 8448 sortOrder = function( a, b ) { 8449 if ( a === b ) { 8450 hasDuplicate = true; 8451 return 0; 8452 } 8453 8454 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8455 return a.compareDocumentPosition ? -1 : 1; 8456 } 8457 8458 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8459 }; 8460 8461 } else { 8462 sortOrder = function( a, b ) { 8463 // The nodes are identical, we can exit early 8464 if ( a === b ) { 8465 hasDuplicate = true; 8466 return 0; 8467 8468 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8469 } else if ( a.sourceIndex && b.sourceIndex ) { 8470 return a.sourceIndex - b.sourceIndex; 8471 } 8472 8473 var al, bl, 8474 ap = [], 8475 bp = [], 8476 aup = a.parentNode, 8477 bup = b.parentNode, 8478 cur = aup; 8479 8480 // If the nodes are siblings (or identical) we can do a quick check 8481 if ( aup === bup ) { 8482 return siblingCheck( a, b ); 8483 8484 // If no parents were found then the nodes are disconnected 8485 } else if ( !aup ) { 8486 return -1; 8487 8488 } else if ( !bup ) { 8489 return 1; 8490 } 8491 8492 // Otherwise they're somewhere else in the tree so we need 8493 // to build up a full list of the parentNodes for comparison 8494 while ( cur ) { 8495 ap.unshift( cur ); 8496 cur = cur.parentNode; 8497 } 8498 8499 cur = bup; 8500 8501 while ( cur ) { 8502 bp.unshift( cur ); 8503 cur = cur.parentNode; 8504 } 8505 8506 al = ap.length; 8507 bl = bp.length; 8508 8509 // Start walking down the tree looking for a discrepancy 8510 for ( var i = 0; i < al && i < bl; i++ ) { 8511 if ( ap[i] !== bp[i] ) { 8512 return siblingCheck( ap[i], bp[i] ); 8513 } 8514 } 8515 8516 // We ended someplace up the tree so do a sibling check 8517 return i === al ? 8518 siblingCheck( a, bp[i], -1 ) : 8519 siblingCheck( ap[i], b, 1 ); 8520 }; 8521 8522 siblingCheck = function( a, b, ret ) { 8523 if ( a === b ) { 8524 return ret; 8525 } 8526 8527 var cur = a.nextSibling; 8528 8529 while ( cur ) { 8530 if ( cur === b ) { 8531 return -1; 8532 } 8533 8534 cur = cur.nextSibling; 8535 } 8536 8537 return 1; 8538 }; 8539 } 8540 8541 // Check to see if the browser returns elements by name when 8542 // querying by getElementById (and provide a workaround) 8543 (function(){ 8544 // We're going to inject a fake input element with a specified name 8545 var form = document.createElement("div"), 8546 id = "script" + (new Date()).getTime(), 8547 root = document.documentElement; 8548 8549 form.innerHTML = "<a name='" + id + "'/>"; 8550 8551 // Inject it into the root element, check its status, and remove it quickly 8552 root.insertBefore( form, root.firstChild ); 8553 8554 // The workaround has to do additional checks after a getElementById 8555 // Which slows things down for other browsers (hence the branching) 8556 if ( document.getElementById( id ) ) { 8557 Expr.find.ID = function( match, context, isXML ) { 8558 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8559 var m = context.getElementById(match[1]); 8560 8561 return m ? 8562 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8563 [m] : 8564 undefined : 8565 []; 8566 } 8567 }; 8568 8569 Expr.filter.ID = function( elem, match ) { 8570 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8571 8572 return elem.nodeType === 1 && node && node.nodeValue === match; 8573 }; 8574 } 8575 8576 root.removeChild( form ); 8577 8578 // release memory in IE 8579 root = form = null; 8580 })(); 8581 8582 (function(){ 8583 // Check to see if the browser returns only elements 8584 // when doing getElementsByTagName("*") 8585 8586 // Create a fake element 8587 var div = document.createElement("div"); 8588 div.appendChild( document.createComment("") ); 8589 8590 // Make sure no comments are found 8591 if ( div.getElementsByTagName("*").length > 0 ) { 8592 Expr.find.TAG = function( match, context ) { 8593 var results = context.getElementsByTagName( match[1] ); 8594 8595 // Filter out possible comments 8596 if ( match[1] === "*" ) { 8597 var tmp = []; 8598 8599 for ( var i = 0; results[i]; i++ ) { 8600 if ( results[i].nodeType === 1 ) { 8601 tmp.push( results[i] ); 8602 } 8603 } 8604 8605 results = tmp; 8606 } 8607 8608 return results; 8609 }; 8610 } 8611 8612 // Check to see if an attribute returns normalized href attributes 8613 div.innerHTML = "<a href='#'></a>"; 8614 8615 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8616 div.firstChild.getAttribute("href") !== "#" ) { 8617 8618 Expr.attrHandle.href = function( elem ) { 8619 return elem.getAttribute( "href", 2 ); 8620 }; 8621 } 8622 8623 // release memory in IE 8624 div = null; 8625 })(); 8626 8627 if ( document.querySelectorAll ) { 8628 (function(){ 8629 var oldSizzle = Sizzle, 8630 div = document.createElement("div"), 8631 id = "__sizzle__"; 8632 8633 div.innerHTML = "<p class='TEST'></p>"; 8634 8635 // Safari can't handle uppercase or unicode characters when 8636 // in quirks mode. 8637 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8638 return; 8639 } 8640 8641 Sizzle = function( query, context, extra, seed ) { 8642 context = context || document; 8643 8644 // Only use querySelectorAll on non-XML documents 8645 // (ID selectors don't work in non-HTML documents) 8646 if ( !seed && !Sizzle.isXML(context) ) { 8647 // See if we find a selector to speed up 8648 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8649 8650 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8651 // Speed-up: Sizzle("TAG") 8652 if ( match[1] ) { 8653 return makeArray( context.getElementsByTagName( query ), extra ); 8654 8655 // Speed-up: Sizzle(".CLASS") 8656 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8657 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8658 } 8659 } 8660 8661 if ( context.nodeType === 9 ) { 8662 // Speed-up: Sizzle("body") 8663 // The body element only exists once, optimize finding it 8664 if ( query === "body" && context.body ) { 8665 return makeArray( [ context.body ], extra ); 8666 8667 // Speed-up: Sizzle("#ID") 8668 } else if ( match && match[3] ) { 8669 var elem = context.getElementById( match[3] ); 8670 8671 // Check parentNode to catch when Blackberry 4.6 returns 8672 // nodes that are no longer in the document #6963 8673 if ( elem && elem.parentNode ) { 8674 // Handle the case where IE and Opera return items 8675 // by name instead of ID 8676 if ( elem.id === match[3] ) { 8677 return makeArray( [ elem ], extra ); 8678 } 8679 8680 } else { 8681 return makeArray( [], extra ); 8682 } 8683 } 8684 8685 try { 8686 return makeArray( context.querySelectorAll(query), extra ); 8687 } catch(qsaError) {} 8688 8689 // qSA works strangely on Element-rooted queries 8690 // We can work around this by specifying an extra ID on the root 8691 // and working up from there (Thanks to Andrew Dupont for the technique) 8692 // IE 8 doesn't work on object elements 8693 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8694 var oldContext = context, 8695 old = context.getAttribute( "id" ), 8696 nid = old || id, 8697 hasParent = context.parentNode, 8698 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8699 8700 if ( !old ) { 8701 context.setAttribute( "id", nid ); 8702 } else { 8703 nid = nid.replace( /'/g, "\\$&" ); 8704 } 8705 if ( relativeHierarchySelector && hasParent ) { 8706 context = context.parentNode; 8707 } 8708 8709 try { 8710 if ( !relativeHierarchySelector || hasParent ) { 8711 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8712 } 8713 8714 } catch(pseudoError) { 8715 } finally { 8716 if ( !old ) { 8717 oldContext.removeAttribute( "id" ); 8718 } 8719 } 8720 } 8721 } 8722 8723 return oldSizzle(query, context, extra, seed); 8724 }; 8725 8726 for ( var prop in oldSizzle ) { 8727 Sizzle[ prop ] = oldSizzle[ prop ]; 8728 } 8729 8730 // release memory in IE 8731 div = null; 8732 })(); 8733 } 8734 8735 (function(){ 8736 var html = document.documentElement, 8737 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 8738 8739 if ( matches ) { 8740 // Check to see if it's possible to do matchesSelector 8741 // on a disconnected node (IE 9 fails this) 8742 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 8743 pseudoWorks = false; 8744 8745 try { 8746 // This should fail with an exception 8747 // Gecko does not error, returns false instead 8748 matches.call( document.documentElement, "[test!='']:sizzle" ); 8749 8750 } catch( pseudoError ) { 8751 pseudoWorks = true; 8752 } 8753 8754 Sizzle.matchesSelector = function( node, expr ) { 8755 // Make sure that attribute selectors are quoted 8756 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 8757 8758 if ( !Sizzle.isXML( node ) ) { 8759 try { 8760 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 8761 var ret = matches.call( node, expr ); 8762 8763 // IE 9's matchesSelector returns false on disconnected nodes 8764 if ( ret || !disconnectedMatch || 8765 // As well, disconnected nodes are said to be in a document 8766 // fragment in IE 9, so check for that 8767 node.document && node.document.nodeType !== 11 ) { 8768 return ret; 8769 } 8770 } 8771 } catch(e) {} 8772 } 8773 8774 return Sizzle(expr, null, null, [node]).length > 0; 8775 }; 8776 } 8777 })(); 8778 8779 (function(){ 8780 var div = document.createElement("div"); 8781 8782 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 8783 8784 // Opera can't find a second classname (in 9.6) 8785 // Also, make sure that getElementsByClassName actually exists 8786 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 8787 return; 8788 } 8789 8790 // Safari caches class attributes, doesn't catch changes (in 3.2) 8791 div.lastChild.className = "e"; 8792 8793 if ( div.getElementsByClassName("e").length === 1 ) { 8794 return; 8795 } 8796 8797 Expr.order.splice(1, 0, "CLASS"); 8798 Expr.find.CLASS = function( match, context, isXML ) { 8799 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 8800 return context.getElementsByClassName(match[1]); 8801 } 8802 }; 8803 8804 // release memory in IE 8805 div = null; 8806 })(); 8807 8808 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8809 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8810 var elem = checkSet[i]; 8811 8812 if ( elem ) { 8813 var match = false; 8814 8815 elem = elem[dir]; 8816 8817 while ( elem ) { 8818 if ( elem[ expando ] === doneName ) { 8819 match = checkSet[elem.sizset]; 8820 break; 8821 } 8822 8823 if ( elem.nodeType === 1 && !isXML ){ 8824 elem[ expando ] = doneName; 8825 elem.sizset = i; 8826 } 8827 8828 if ( elem.nodeName.toLowerCase() === cur ) { 8829 match = elem; 8830 break; 8831 } 8832 8833 elem = elem[dir]; 8834 } 8835 8836 checkSet[i] = match; 8837 } 8838 } 8839 } 8840 8841 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8842 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8843 var elem = checkSet[i]; 8844 8845 if ( elem ) { 8846 var match = false; 8847 8848 elem = elem[dir]; 8849 8850 while ( elem ) { 8851 if ( elem[ expando ] === doneName ) { 8852 match = checkSet[elem.sizset]; 8853 break; 8854 } 8855 8856 if ( elem.nodeType === 1 ) { 8857 if ( !isXML ) { 8858 elem[ expando ] = doneName; 8859 elem.sizset = i; 8860 } 8861 8862 if ( typeof cur !== "string" ) { 8863 if ( elem === cur ) { 8864 match = true; 8865 break; 8866 } 8867 8868 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 8869 match = elem; 8870 break; 8871 } 8872 } 8873 8874 elem = elem[dir]; 8875 } 8876 8877 checkSet[i] = match; 8878 } 8879 } 8880 } 8881 8882 if ( document.documentElement.contains ) { 8883 Sizzle.contains = function( a, b ) { 8884 return a !== b && (a.contains ? a.contains(b) : true); 8885 }; 8886 8887 } else if ( document.documentElement.compareDocumentPosition ) { 8888 Sizzle.contains = function( a, b ) { 8889 return !!(a.compareDocumentPosition(b) & 16); 8890 }; 8891 8892 } else { 8893 Sizzle.contains = function() { 8894 return false; 8895 }; 8896 } 8897 8898 Sizzle.isXML = function( elem ) { 8899 // documentElement is verified for cases where it doesn't yet exist 8900 // (such as loading iframes in IE - #4833) 8901 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 8902 8903 return documentElement ? documentElement.nodeName !== "HTML" : false; 8904 }; 8905 8906 var posProcess = function( selector, context, seed ) { 8907 var match, 8908 tmpSet = [], 8909 later = "", 8910 root = context.nodeType ? [context] : context; 8911 8912 // Position selectors must be done after the filter 8913 // And so must :not(positional) so we move all PSEUDOs to the end 8914 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 8915 later += match[0]; 8916 selector = selector.replace( Expr.match.PSEUDO, "" ); 8917 } 8918 8919 selector = Expr.relative[selector] ? selector + "*" : selector; 8920 8921 for ( var i = 0, l = root.length; i < l; i++ ) { 8922 Sizzle( selector, root[i], tmpSet, seed ); 8923 } 8924 8925 return Sizzle.filter( later, tmpSet ); 8926 }; 8927 8928 // EXPOSE 8929 8930 window.tinymce.dom.Sizzle = Sizzle; 8931 8932 })(); 8933 8934 8935 (function(tinymce) { 8936 tinymce.dom.Element = function(id, settings) { 8937 var t = this, dom, el; 8938 8939 t.settings = settings = settings || {}; 8940 t.id = id; 8941 t.dom = dom = settings.dom || tinymce.DOM; 8942 8943 // Only IE leaks DOM references, this is a lot faster 8944 if (!tinymce.isIE) 8945 el = dom.get(t.id); 8946 8947 tinymce.each( 8948 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 8949 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 8950 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 8951 'isHidden,setHTML,get').split(/,/), function(k) { 8952 t[k] = function() { 8953 var a = [id], i; 8954 8955 for (i = 0; i < arguments.length; i++) 8956 a.push(arguments[i]); 8957 8958 a = dom[k].apply(dom, a); 8959 t.update(k); 8960 8961 return a; 8962 }; 8963 } 8964 ); 8965 8966 tinymce.extend(t, { 8967 on : function(n, f, s) { 8968 return tinymce.dom.Event.add(t.id, n, f, s); 8969 }, 8970 8971 getXY : function() { 8972 return { 8973 x : parseInt(t.getStyle('left')), 8974 y : parseInt(t.getStyle('top')) 8975 }; 8976 }, 8977 8978 getSize : function() { 8979 var n = dom.get(t.id); 8980 8981 return { 8982 w : parseInt(t.getStyle('width') || n.clientWidth), 8983 h : parseInt(t.getStyle('height') || n.clientHeight) 8984 }; 8985 }, 8986 8987 moveTo : function(x, y) { 8988 t.setStyles({left : x, top : y}); 8989 }, 8990 8991 moveBy : function(x, y) { 8992 var p = t.getXY(); 8993 8994 t.moveTo(p.x + x, p.y + y); 8995 }, 8996 8997 resizeTo : function(w, h) { 8998 t.setStyles({width : w, height : h}); 8999 }, 9000 9001 resizeBy : function(w, h) { 9002 var s = t.getSize(); 9003 9004 t.resizeTo(s.w + w, s.h + h); 9005 }, 9006 9007 update : function(k) { 9008 var b; 9009 9010 if (tinymce.isIE6 && settings.blocker) { 9011 k = k || ''; 9012 9013 // Ignore getters 9014 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9015 return; 9016 9017 // Remove blocker on remove 9018 if (k == 'remove') { 9019 dom.remove(t.blocker); 9020 return; 9021 } 9022 9023 if (!t.blocker) { 9024 t.blocker = dom.uniqueId(); 9025 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9026 dom.setStyle(b, 'opacity', 0); 9027 } else 9028 b = dom.get(t.blocker); 9029 9030 dom.setStyles(b, { 9031 left : t.getStyle('left', 1), 9032 top : t.getStyle('top', 1), 9033 width : t.getStyle('width', 1), 9034 height : t.getStyle('height', 1), 9035 display : t.getStyle('display', 1), 9036 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9037 }); 9038 } 9039 } 9040 }); 9041 }; 9042 })(tinymce); 9043 9044 (function(tinymce) { 9045 function trimNl(s) { 9046 return s.replace(/[\n\r]+/g, ''); 9047 }; 9048 9049 // Shorten names 9050 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9051 9052 tinymce.create('tinymce.dom.Selection', { 9053 Selection : function(dom, win, serializer, editor) { 9054 var t = this; 9055 9056 t.dom = dom; 9057 t.win = win; 9058 t.serializer = serializer; 9059 t.editor = editor; 9060 9061 // Add events 9062 each([ 9063 'onBeforeSetContent', 9064 9065 'onBeforeGetContent', 9066 9067 'onSetContent', 9068 9069 'onGetContent' 9070 ], function(e) { 9071 t[e] = new tinymce.util.Dispatcher(t); 9072 }); 9073 9074 // No W3C Range support 9075 if (!t.win.getSelection) 9076 t.tridentSel = new tinymce.dom.TridentSelection(t); 9077 9078 if (tinymce.isIE && dom.boxModel) 9079 this._fixIESelection(); 9080 9081 // Prevent leaks 9082 tinymce.addUnload(t.destroy, t); 9083 }, 9084 9085 setCursorLocation: function(node, offset) { 9086 var t = this; var r = t.dom.createRng(); 9087 r.setStart(node, offset); 9088 r.setEnd(node, offset); 9089 t.setRng(r); 9090 t.collapse(false); 9091 }, 9092 getContent : function(s) { 9093 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9094 9095 s = s || {}; 9096 wb = wa = ''; 9097 s.get = true; 9098 s.format = s.format || 'html'; 9099 s.forced_root_block = ''; 9100 t.onBeforeGetContent.dispatch(t, s); 9101 9102 if (s.format == 'text') 9103 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9104 9105 if (r.cloneContents) { 9106 n = r.cloneContents(); 9107 9108 if (n) 9109 e.appendChild(n); 9110 } else if (is(r.item) || is(r.htmlText)) { 9111 // IE will produce invalid markup if elements are present that 9112 // it doesn't understand like custom elements or HTML5 elements. 9113 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9114 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9115 e.removeChild(e.firstChild); 9116 } else 9117 e.innerHTML = r.toString(); 9118 9119 // Keep whitespace before and after 9120 if (/^\s/.test(e.innerHTML)) 9121 wb = ' '; 9122 9123 if (/\s+$/.test(e.innerHTML)) 9124 wa = ' '; 9125 9126 s.getInner = true; 9127 9128 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9129 t.onGetContent.dispatch(t, s); 9130 9131 return s.content; 9132 }, 9133 9134 setContent : function(content, args) { 9135 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9136 9137 args = args || {format : 'html'}; 9138 args.set = true; 9139 content = args.content = content; 9140 9141 // Dispatch before set content event 9142 if (!args.no_events) 9143 self.onBeforeSetContent.dispatch(self, args); 9144 9145 content = args.content; 9146 9147 if (rng.insertNode) { 9148 // Make caret marker since insertNode places the caret in the beginning of text after insert 9149 content += '<span id="__caret">_</span>'; 9150 9151 // Delete and insert new node 9152 if (rng.startContainer == doc && rng.endContainer == doc) { 9153 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9154 doc.body.innerHTML = content; 9155 } else { 9156 rng.deleteContents(); 9157 9158 if (doc.body.childNodes.length === 0) { 9159 doc.body.innerHTML = content; 9160 } else { 9161 // createContextualFragment doesn't exists in IE 9 DOMRanges 9162 if (rng.createContextualFragment) { 9163 rng.insertNode(rng.createContextualFragment(content)); 9164 } else { 9165 // Fake createContextualFragment call in IE 9 9166 frag = doc.createDocumentFragment(); 9167 temp = doc.createElement('div'); 9168 9169 frag.appendChild(temp); 9170 temp.outerHTML = content; 9171 9172 rng.insertNode(frag); 9173 } 9174 } 9175 } 9176 9177 // Move to caret marker 9178 caretNode = self.dom.get('__caret'); 9179 9180 // Make sure we wrap it compleatly, Opera fails with a simple select call 9181 rng = doc.createRange(); 9182 rng.setStartBefore(caretNode); 9183 rng.setEndBefore(caretNode); 9184 self.setRng(rng); 9185 9186 // Remove the caret position 9187 self.dom.remove('__caret'); 9188 9189 try { 9190 self.setRng(rng); 9191 } catch (ex) { 9192 // Might fail on Opera for some odd reason 9193 } 9194 } else { 9195 if (rng.item) { 9196 // Delete content and get caret text selection 9197 doc.execCommand('Delete', false, null); 9198 rng = self.getRng(); 9199 } 9200 9201 // Explorer removes spaces from the beginning of pasted contents 9202 if (/^\s+/.test(content)) { 9203 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9204 self.dom.remove('__mce_tmp'); 9205 } else 9206 rng.pasteHTML(content); 9207 } 9208 9209 // Dispatch set content event 9210 if (!args.no_events) 9211 self.onSetContent.dispatch(self, args); 9212 }, 9213 9214 getStart : function() { 9215 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9216 9217 if (rng.duplicate || rng.item) { 9218 // Control selection, return first item 9219 if (rng.item) 9220 return rng.item(0); 9221 9222 // Get start element 9223 checkRng = rng.duplicate(); 9224 checkRng.collapse(1); 9225 startElement = checkRng.parentElement(); 9226 if (startElement.ownerDocument !== self.dom.doc) { 9227 startElement = self.dom.getRoot(); 9228 } 9229 9230 // Check if range parent is inside the start element, then return the inner parent element 9231 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9232 parentElement = node = rng.parentElement(); 9233 while (node = node.parentNode) { 9234 if (node == startElement) { 9235 startElement = parentElement; 9236 break; 9237 } 9238 } 9239 9240 return startElement; 9241 } else { 9242 startElement = rng.startContainer; 9243 9244 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9245 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9246 9247 if (startElement && startElement.nodeType == 3) 9248 return startElement.parentNode; 9249 9250 return startElement; 9251 } 9252 }, 9253 9254 getEnd : function() { 9255 var self = this, rng = self.getRng(), endElement, endOffset; 9256 9257 if (rng.duplicate || rng.item) { 9258 if (rng.item) 9259 return rng.item(0); 9260 9261 rng = rng.duplicate(); 9262 rng.collapse(0); 9263 endElement = rng.parentElement(); 9264 if (endElement.ownerDocument !== self.dom.doc) { 9265 endElement = self.dom.getRoot(); 9266 } 9267 9268 if (endElement && endElement.nodeName == 'BODY') 9269 return endElement.lastChild || endElement; 9270 9271 return endElement; 9272 } else { 9273 endElement = rng.endContainer; 9274 endOffset = rng.endOffset; 9275 9276 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9277 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9278 9279 if (endElement && endElement.nodeType == 3) 9280 return endElement.parentNode; 9281 9282 return endElement; 9283 } 9284 }, 9285 9286 getBookmark : function(type, normalized) { 9287 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9288 9289 function findIndex(name, element) { 9290 var index = 0; 9291 9292 each(dom.select(name), function(node, i) { 9293 if (node == element) 9294 index = i; 9295 }); 9296 9297 return index; 9298 }; 9299 9300 function normalizeTableCellSelection(rng) { 9301 function moveEndPoint(start) { 9302 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9303 9304 container = rng[prefix + 'Container']; 9305 offset = rng[prefix + 'Offset']; 9306 9307 if (container.nodeType == 1 && container.nodeName == "TR") { 9308 childNodes = container.childNodes; 9309 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9310 if (container) { 9311 offset = start ? 0 : container.childNodes.length; 9312 rng['set' + (start ? 'Start' : 'End')](container, offset); 9313 } 9314 } 9315 }; 9316 9317 moveEndPoint(true); 9318 moveEndPoint(); 9319 9320 return rng; 9321 }; 9322 9323 function getLocation() { 9324 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9325 9326 function getPoint(rng, start) { 9327 var container = rng[start ? 'startContainer' : 'endContainer'], 9328 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9329 9330 if (container.nodeType == 3) { 9331 if (normalized) { 9332 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9333 offset += node.nodeValue.length; 9334 } 9335 9336 point.push(offset); 9337 } else { 9338 childNodes = container.childNodes; 9339 9340 if (offset >= childNodes.length && childNodes.length) { 9341 after = 1; 9342 offset = Math.max(0, childNodes.length - 1); 9343 } 9344 9345 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9346 } 9347 9348 for (; container && container != root; container = container.parentNode) 9349 point.push(t.dom.nodeIndex(container, normalized)); 9350 9351 return point; 9352 }; 9353 9354 bookmark.start = getPoint(rng, true); 9355 9356 if (!t.isCollapsed()) 9357 bookmark.end = getPoint(rng); 9358 9359 return bookmark; 9360 }; 9361 9362 if (type == 2) { 9363 if (t.tridentSel) 9364 return t.tridentSel.getBookmark(type); 9365 9366 return getLocation(); 9367 } 9368 9369 // Handle simple range 9370 if (type) 9371 return {rng : t.getRng()}; 9372 9373 rng = t.getRng(); 9374 id = dom.uniqueId(); 9375 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9376 styles = 'overflow:hidden;line-height:0px'; 9377 9378 // Explorer method 9379 if (rng.duplicate || rng.item) { 9380 // Text selection 9381 if (!rng.item) { 9382 rng2 = rng.duplicate(); 9383 9384 try { 9385 // Insert start marker 9386 rng.collapse(); 9387 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9388 9389 // Insert end marker 9390 if (!collapsed) { 9391 rng2.collapse(false); 9392 9393 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9394 rng.moveToElementText(rng2.parentElement()); 9395 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9396 rng2.move('character', -1); 9397 9398 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9399 } 9400 } catch (ex) { 9401 // IE might throw unspecified error so lets ignore it 9402 return null; 9403 } 9404 } else { 9405 // Control selection 9406 element = rng.item(0); 9407 name = element.nodeName; 9408 9409 return {name : name, index : findIndex(name, element)}; 9410 } 9411 } else { 9412 element = t.getNode(); 9413 name = element.nodeName; 9414 if (name == 'IMG') 9415 return {name : name, index : findIndex(name, element)}; 9416 9417 // W3C method 9418 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9419 9420 // Insert end marker 9421 if (!collapsed) { 9422 rng2.collapse(false); 9423 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9424 } 9425 9426 rng = normalizeTableCellSelection(rng); 9427 rng.collapse(true); 9428 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9429 } 9430 9431 t.moveToBookmark({id : id, keep : 1}); 9432 9433 return {id : id}; 9434 }, 9435 9436 moveToBookmark : function(bookmark) { 9437 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; 9438 9439 function setEndPoint(start) { 9440 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9441 9442 if (point) { 9443 offset = point[0]; 9444 9445 // Find container node 9446 for (node = root, i = point.length - 1; i >= 1; i--) { 9447 children = node.childNodes; 9448 9449 if (point[i] > children.length - 1) 9450 return; 9451 9452 node = children[point[i]]; 9453 } 9454 9455 // Move text offset to best suitable location 9456 if (node.nodeType === 3) 9457 offset = Math.min(point[0], node.nodeValue.length); 9458 9459 // Move element offset to best suitable location 9460 if (node.nodeType === 1) 9461 offset = Math.min(point[0], node.childNodes.length); 9462 9463 // Set offset within container node 9464 if (start) 9465 rng.setStart(node, offset); 9466 else 9467 rng.setEnd(node, offset); 9468 } 9469 9470 return true; 9471 }; 9472 9473 function restoreEndPoint(suffix) { 9474 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9475 9476 if (marker) { 9477 node = marker.parentNode; 9478 9479 if (suffix == 'start') { 9480 if (!keep) { 9481 idx = dom.nodeIndex(marker); 9482 } else { 9483 node = marker.firstChild; 9484 idx = 1; 9485 } 9486 9487 startContainer = endContainer = node; 9488 startOffset = endOffset = idx; 9489 } else { 9490 if (!keep) { 9491 idx = dom.nodeIndex(marker); 9492 } else { 9493 node = marker.firstChild; 9494 idx = 1; 9495 } 9496 9497 endContainer = node; 9498 endOffset = idx; 9499 } 9500 9501 if (!keep) { 9502 prev = marker.previousSibling; 9503 next = marker.nextSibling; 9504 9505 // Remove all marker text nodes 9506 each(tinymce.grep(marker.childNodes), function(node) { 9507 if (node.nodeType == 3) 9508 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9509 }); 9510 9511 // Remove marker but keep children if for example contents where inserted into the marker 9512 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9513 while (marker = dom.get(bookmark.id + '_' + suffix)) 9514 dom.remove(marker, 1); 9515 9516 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9517 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact 9518 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9519 idx = prev.nodeValue.length; 9520 prev.appendData(next.nodeValue); 9521 dom.remove(next); 9522 9523 if (suffix == 'start') { 9524 startContainer = endContainer = prev; 9525 startOffset = endOffset = idx; 9526 } else { 9527 endContainer = prev; 9528 endOffset = idx; 9529 } 9530 } 9531 } 9532 } 9533 }; 9534 9535 function addBogus(node) { 9536 // Adds a bogus BR element for empty block elements 9537 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9538 node.innerHTML = '<br data-mce-bogus="1" />'; 9539 9540 return node; 9541 }; 9542 9543 if (bookmark) { 9544 if (bookmark.start) { 9545 rng = dom.createRng(); 9546 root = dom.getRoot(); 9547 9548 if (t.tridentSel) 9549 return t.tridentSel.moveToBookmark(bookmark); 9550 9551 if (setEndPoint(true) && setEndPoint()) { 9552 t.setRng(rng); 9553 } 9554 } else if (bookmark.id) { 9555 // Restore start/end points 9556 restoreEndPoint('start'); 9557 restoreEndPoint('end'); 9558 9559 if (startContainer) { 9560 rng = dom.createRng(); 9561 rng.setStart(addBogus(startContainer), startOffset); 9562 rng.setEnd(addBogus(endContainer), endOffset); 9563 t.setRng(rng); 9564 } 9565 } else if (bookmark.name) { 9566 t.select(dom.select(bookmark.name)[bookmark.index]); 9567 } else if (bookmark.rng) 9568 t.setRng(bookmark.rng); 9569 } 9570 }, 9571 9572 select : function(node, content) { 9573 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9574 9575 function setPoint(node, start) { 9576 var walker = new TreeWalker(node, node); 9577 9578 do { 9579 // Text node 9580 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9581 if (start) 9582 rng.setStart(node, 0); 9583 else 9584 rng.setEnd(node, node.nodeValue.length); 9585 9586 return; 9587 } 9588 9589 // BR element 9590 if (node.nodeName == 'BR') { 9591 if (start) 9592 rng.setStartBefore(node); 9593 else 9594 rng.setEndBefore(node); 9595 9596 return; 9597 } 9598 } while (node = (start ? walker.next() : walker.prev())); 9599 }; 9600 9601 if (node) { 9602 idx = dom.nodeIndex(node); 9603 rng.setStart(node.parentNode, idx); 9604 rng.setEnd(node.parentNode, idx + 1); 9605 9606 // Find first/last text node or BR element 9607 if (content) { 9608 setPoint(node, 1); 9609 setPoint(node); 9610 } 9611 9612 t.setRng(rng); 9613 } 9614 9615 return node; 9616 }, 9617 9618 isCollapsed : function() { 9619 var t = this, r = t.getRng(), s = t.getSel(); 9620 9621 if (!r || r.item) 9622 return false; 9623 9624 if (r.compareEndPoints) 9625 return r.compareEndPoints('StartToEnd', r) === 0; 9626 9627 return !s || r.collapsed; 9628 }, 9629 9630 collapse : function(to_start) { 9631 var self = this, rng = self.getRng(), node; 9632 9633 // Control range on IE 9634 if (rng.item) { 9635 node = rng.item(0); 9636 rng = self.win.document.body.createTextRange(); 9637 rng.moveToElementText(node); 9638 } 9639 9640 rng.collapse(!!to_start); 9641 self.setRng(rng); 9642 }, 9643 9644 getSel : function() { 9645 var t = this, w = this.win; 9646 9647 return w.getSelection ? w.getSelection() : w.document.selection; 9648 }, 9649 9650 getRng : function(w3c) { 9651 var self = this, selection, rng, elm, doc = self.win.document; 9652 9653 // Found tridentSel object then we need to use that one 9654 if (w3c && self.tridentSel) { 9655 return self.tridentSel.getRangeAt(0); 9656 } 9657 9658 try { 9659 if (selection = self.getSel()) { 9660 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9661 } 9662 } catch (ex) { 9663 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9664 } 9665 9666 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9667 if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { 9668 elm = doc.selection.createRange().item(0); 9669 rng = doc.createRange(); 9670 rng.setStartBefore(elm); 9671 rng.setEndAfter(elm); 9672 } 9673 9674 // No range found then create an empty one 9675 // This can occur when the editor is placed in a hidden container element on Gecko 9676 // Or on IE when there was an exception 9677 if (!rng) { 9678 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9679 } 9680 9681 // If range is at start of document then move it to start of body 9682 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9683 elm = self.dom.getRoot(); 9684 rng.setStart(elm, 0); 9685 rng.setEnd(elm, 0); 9686 } 9687 9688 if (self.selectedRange && self.explicitRange) { 9689 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9690 // Safari, Opera and Chrome only ever select text which causes the range to change. 9691 // This lets us use the originally set range if the selection hasn't been changed by the user. 9692 rng = self.explicitRange; 9693 } else { 9694 self.selectedRange = null; 9695 self.explicitRange = null; 9696 } 9697 } 9698 9699 return rng; 9700 }, 9701 9702 setRng : function(r, forward) { 9703 var s, t = this; 9704 9705 if (!t.tridentSel) { 9706 s = t.getSel(); 9707 9708 if (s) { 9709 t.explicitRange = r; 9710 9711 try { 9712 s.removeAllRanges(); 9713 } catch (ex) { 9714 // IE9 might throw errors here don't know why 9715 } 9716 9717 s.addRange(r); 9718 9719 // Forward is set to false and we have an extend function 9720 if (forward === false && s.extend) { 9721 s.collapse(r.endContainer, r.endOffset); 9722 s.extend(r.startContainer, r.startOffset); 9723 } 9724 9725 // adding range isn't always successful so we need to check range count otherwise an exception can occur 9726 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 9727 } 9728 } else { 9729 // Is W3C Range 9730 if (r.cloneRange) { 9731 try { 9732 t.tridentSel.addRange(r); 9733 return; 9734 } catch (ex) { 9735 //IE9 throws an error here if called before selection is placed in the editor 9736 } 9737 } 9738 9739 // Is IE specific range 9740 try { 9741 r.select(); 9742 } catch (ex) { 9743 // Needed for some odd IE bug #1843306 9744 } 9745 } 9746 }, 9747 9748 setNode : function(n) { 9749 var t = this; 9750 9751 t.setContent(t.dom.getOuterHTML(n)); 9752 9753 return n; 9754 }, 9755 9756 getNode : function() { 9757 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 9758 9759 function skipEmptyTextNodes(n, forwards) { 9760 var orig = n; 9761 while (n && n.nodeType === 3 && n.length === 0) { 9762 n = forwards ? n.nextSibling : n.previousSibling; 9763 } 9764 return n || orig; 9765 }; 9766 9767 // Range maybe lost after the editor is made visible again 9768 if (!rng) 9769 return t.dom.getRoot(); 9770 9771 if (rng.setStart) { 9772 elm = rng.commonAncestorContainer; 9773 9774 // Handle selection a image or other control like element such as anchors 9775 if (!rng.collapsed) { 9776 if (rng.startContainer == rng.endContainer) { 9777 if (rng.endOffset - rng.startOffset < 2) { 9778 if (rng.startContainer.hasChildNodes()) 9779 elm = rng.startContainer.childNodes[rng.startOffset]; 9780 } 9781 } 9782 9783 // If the anchor node is a element instead of a text node then return this element 9784 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 9785 // return sel.anchorNode.childNodes[sel.anchorOffset]; 9786 9787 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 9788 // This happens when you double click an underlined word in FireFox. 9789 if (start.nodeType === 3 && end.nodeType === 3) { 9790 if (start.length === rng.startOffset) { 9791 start = skipEmptyTextNodes(start.nextSibling, true); 9792 } else { 9793 start = start.parentNode; 9794 } 9795 if (rng.endOffset === 0) { 9796 end = skipEmptyTextNodes(end.previousSibling, false); 9797 } else { 9798 end = end.parentNode; 9799 } 9800 9801 if (start && start === end) 9802 return start; 9803 } 9804 } 9805 9806 if (elm && elm.nodeType == 3) 9807 return elm.parentNode; 9808 9809 return elm; 9810 } 9811 9812 return rng.item ? rng.item(0) : rng.parentElement(); 9813 }, 9814 9815 getSelectedBlocks : function(st, en) { 9816 var t = this, dom = t.dom, sb, eb, n, bl = []; 9817 9818 sb = dom.getParent(st || t.getStart(), dom.isBlock); 9819 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 9820 9821 if (sb) 9822 bl.push(sb); 9823 9824 if (sb && eb && sb != eb) { 9825 n = sb; 9826 9827 var walker = new TreeWalker(sb, dom.getRoot()); 9828 while ((n = walker.next()) && n != eb) { 9829 if (dom.isBlock(n)) 9830 bl.push(n); 9831 } 9832 } 9833 9834 if (eb && sb != eb) 9835 bl.push(eb); 9836 9837 return bl; 9838 }, 9839 9840 isForward: function(){ 9841 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 9842 9843 // No support for selection direction then always return true 9844 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 9845 return true; 9846 } 9847 9848 anchorRange = dom.createRng(); 9849 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 9850 anchorRange.collapse(true); 9851 9852 focusRange = dom.createRng(); 9853 focusRange.setStart(sel.focusNode, sel.focusOffset); 9854 focusRange.collapse(true); 9855 9856 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 9857 }, 9858 9859 normalize : function() { 9860 var self = this, rng, normalized, collapsed, node, sibling; 9861 9862 function normalizeEndPoint(start) { 9863 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 9864 9865 function hasBrBeforeAfter(node, left) { 9866 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 9867 9868 while (node = walker[left ? 'prev' : 'next']()) { 9869 if (node.nodeName === "BR") { 9870 return true; 9871 } 9872 } 9873 }; 9874 9875 // Walks the dom left/right to find a suitable text node to move the endpoint into 9876 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 9877 function findTextNodeRelative(left, startNode) { 9878 var walker, lastInlineElement; 9879 9880 startNode = startNode || container; 9881 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 9882 9883 // Walk left until we hit a text node we can move to or a block/br/img 9884 while (node = walker[left ? 'prev' : 'next']()) { 9885 // Found text node that has a length 9886 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9887 container = node; 9888 offset = left ? node.nodeValue.length : 0; 9889 normalized = true; 9890 return; 9891 } 9892 9893 // Break if we find a block or a BR/IMG/INPUT etc 9894 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9895 return; 9896 } 9897 9898 lastInlineElement = node; 9899 } 9900 9901 // Only fetch the last inline element when in caret mode for now 9902 if (collapsed && lastInlineElement) { 9903 container = lastInlineElement; 9904 normalized = true; 9905 offset = 0; 9906 } 9907 }; 9908 9909 container = rng[(start ? 'start' : 'end') + 'Container']; 9910 offset = rng[(start ? 'start' : 'end') + 'Offset']; 9911 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 9912 9913 // If the container is a document move it to the body element 9914 if (container.nodeType === 9) { 9915 container = dom.getRoot(); 9916 offset = 0; 9917 } 9918 9919 // If the container is body try move it into the closest text node or position 9920 if (container === body) { 9921 // If start is before/after a image, table etc 9922 if (start) { 9923 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 9924 if (node) { 9925 nodeName = node.nodeName.toLowerCase(); 9926 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 9927 return; 9928 } 9929 } 9930 } 9931 9932 // Resolve the index 9933 if (container.hasChildNodes()) { 9934 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 9935 offset = 0; 9936 9937 // Don't walk into elements that doesn't have any child nodes like a IMG 9938 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 9939 // Walk the DOM to find a text node to place the caret at or a BR 9940 node = container; 9941 walker = new TreeWalker(container, body); 9942 9943 do { 9944 // Found a text node use that position 9945 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9946 offset = start ? 0 : node.nodeValue.length; 9947 container = node; 9948 normalized = true; 9949 break; 9950 } 9951 9952 // Found a BR/IMG element that we can place the caret before 9953 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9954 offset = dom.nodeIndex(node); 9955 container = node.parentNode; 9956 9957 // Put caret after image when moving the end point 9958 if (node.nodeName == "IMG" && !start) { 9959 offset++; 9960 } 9961 9962 normalized = true; 9963 break; 9964 } 9965 } while (node = (start ? walker.next() : walker.prev())); 9966 } 9967 } 9968 } 9969 9970 // Lean the caret to the left if possible 9971 if (collapsed) { 9972 // So this: <b>x</b><i>|x</i> 9973 // Becomes: <b>x|</b><i>x</i> 9974 // Seems that only gecko has issues with this 9975 if (container.nodeType === 3 && offset === 0) { 9976 findTextNodeRelative(true); 9977 } 9978 9979 // Lean left into empty inline elements when the caret is before a BR 9980 // So this: <i><b></b><i>|<br></i> 9981 // Becomes: <i><b>|</b><i><br></i> 9982 // Seems that only gecko has issues with this 9983 if (container.nodeType === 1) { 9984 node = container.childNodes[offset]; 9985 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 9986 findTextNodeRelative(true, container.childNodes[offset]); 9987 } 9988 } 9989 } 9990 9991 // Lean the start of the selection right if possible 9992 // So this: x[<b>x]</b> 9993 // Becomes: x<b>[x]</b> 9994 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 9995 findTextNodeRelative(false); 9996 } 9997 9998 // Set endpoint if it was normalized 9999 if (normalized) 10000 rng['set' + (start ? 'Start' : 'End')](container, offset); 10001 }; 10002 10003 // Normalize only on non IE browsers for now 10004 if (tinymce.isIE) 10005 return; 10006 10007 rng = self.getRng(); 10008 collapsed = rng.collapsed; 10009 10010 // Normalize the end points 10011 normalizeEndPoint(true); 10012 10013 if (!collapsed) 10014 normalizeEndPoint(); 10015 10016 // Set the selection if it was normalized 10017 if (normalized) { 10018 // If it was collapsed then make sure it still is 10019 if (collapsed) { 10020 rng.collapse(true); 10021 } 10022 10023 //console.log(self.dom.dumpRng(rng)); 10024 self.setRng(rng, self.isForward()); 10025 } 10026 }, 10027 10028 selectorChanged: function(selector, callback) { 10029 var self = this, currentSelectors; 10030 10031 if (!self.selectorChangedData) { 10032 self.selectorChangedData = {}; 10033 currentSelectors = {}; 10034 10035 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10036 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10037 10038 // Check for new matching selectors 10039 each(self.selectorChangedData, function(callbacks, selector) { 10040 each(parents, function(node) { 10041 if (dom.is(node, selector)) { 10042 if (!currentSelectors[selector]) { 10043 // Execute callbacks 10044 each(callbacks, function(callback) { 10045 callback(true, {node: node, selector: selector, parents: parents}); 10046 }); 10047 10048 currentSelectors[selector] = callbacks; 10049 } 10050 10051 matchedSelectors[selector] = callbacks; 10052 return false; 10053 } 10054 }); 10055 }); 10056 10057 // Check if current selectors still match 10058 each(currentSelectors, function(callbacks, selector) { 10059 if (!matchedSelectors[selector]) { 10060 delete currentSelectors[selector]; 10061 10062 each(callbacks, function(callback) { 10063 callback(false, {node: node, selector: selector, parents: parents}); 10064 }); 10065 } 10066 }); 10067 }); 10068 } 10069 10070 // Add selector listeners 10071 if (!self.selectorChangedData[selector]) { 10072 self.selectorChangedData[selector] = []; 10073 } 10074 10075 self.selectorChangedData[selector].push(callback); 10076 10077 return self; 10078 }, 10079 10080 destroy : function(manual) { 10081 var self = this; 10082 10083 self.win = null; 10084 10085 // Manual destroy then remove unload handler 10086 if (!manual) 10087 tinymce.removeUnload(self.destroy); 10088 }, 10089 10090 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10091 _fixIESelection : function() { 10092 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10093 10094 // Return range from point or null if it failed 10095 function rngFromPoint(x, y) { 10096 var rng = body.createTextRange(); 10097 10098 try { 10099 rng.moveToPoint(x, y); 10100 } catch (ex) { 10101 // IE sometimes throws and exception, so lets just ignore it 10102 rng = null; 10103 } 10104 10105 return rng; 10106 }; 10107 10108 // Fires while the selection is changing 10109 function selectionChange(e) { 10110 var pointRng; 10111 10112 // Check if the button is down or not 10113 if (e.button) { 10114 // Create range from mouse position 10115 pointRng = rngFromPoint(e.x, e.y); 10116 10117 if (pointRng) { 10118 // Check if pointRange is before/after selection then change the endPoint 10119 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10120 pointRng.setEndPoint('StartToStart', startRng); 10121 else 10122 pointRng.setEndPoint('EndToEnd', startRng); 10123 10124 pointRng.select(); 10125 } 10126 } else 10127 endSelection(); 10128 } 10129 10130 // Removes listeners 10131 function endSelection() { 10132 var rng = doc.selection.createRange(); 10133 10134 // If the range is collapsed then use the last start range 10135 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10136 startRng.select(); 10137 10138 dom.unbind(doc, 'mouseup', endSelection); 10139 dom.unbind(doc, 'mousemove', selectionChange); 10140 startRng = started = 0; 10141 }; 10142 10143 // Make HTML element unselectable since we are going to handle selection by hand 10144 doc.documentElement.unselectable = true; 10145 10146 // Detect when user selects outside BODY 10147 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10148 if (e.target.nodeName === 'HTML') { 10149 if (started) 10150 endSelection(); 10151 10152 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10153 htmlElm = doc.documentElement; 10154 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10155 return; 10156 10157 started = 1; 10158 // Setup start position 10159 startRng = rngFromPoint(e.x, e.y); 10160 if (startRng) { 10161 // Listen for selection change events 10162 dom.bind(doc, 'mouseup', endSelection); 10163 dom.bind(doc, 'mousemove', selectionChange); 10164 10165 dom.win.focus(); 10166 startRng.select(); 10167 } 10168 } 10169 }); 10170 } 10171 }); 10172 })(tinymce); 10173 10174 (function(tinymce) { 10175 tinymce.dom.Serializer = function(settings, dom, schema) { 10176 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10177 10178 // Support the old apply_source_formatting option 10179 if (!settings.apply_source_formatting) 10180 settings.indent = false; 10181 10182 // Default DOM and Schema if they are undefined 10183 dom = dom || tinymce.DOM; 10184 schema = schema || new tinymce.html.Schema(settings); 10185 settings.entity_encoding = settings.entity_encoding || 'named'; 10186 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10187 10188 onPreProcess = new tinymce.util.Dispatcher(self); 10189 10190 onPostProcess = new tinymce.util.Dispatcher(self); 10191 10192 htmlParser = new tinymce.html.DomParser(settings, schema); 10193 10194 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10195 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10196 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10197 10198 while (i--) { 10199 node = nodes[i]; 10200 10201 value = node.attributes.map[internalName]; 10202 if (value !== undef) { 10203 // Set external name to internal value and remove internal 10204 node.attr(name, value.length > 0 ? value : null); 10205 node.attr(internalName, null); 10206 } else { 10207 // No internal attribute found then convert the value we have in the DOM 10208 value = node.attributes.map[name]; 10209 10210 if (name === "style") 10211 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10212 else if (urlConverter) 10213 value = urlConverter.call(urlConverterScope, value, name, node.name); 10214 10215 node.attr(name, value.length > 0 ? value : null); 10216 } 10217 } 10218 }); 10219 10220 // Remove internal classes mceItem<..> or mceSelected 10221 htmlParser.addAttributeFilter('class', function(nodes, name) { 10222 var i = nodes.length, node, value; 10223 10224 while (i--) { 10225 node = nodes[i]; 10226 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10227 node.attr('class', value.length > 0 ? value : null); 10228 } 10229 }); 10230 10231 // Remove bookmark elements 10232 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10233 var i = nodes.length, node; 10234 10235 while (i--) { 10236 node = nodes[i]; 10237 10238 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10239 node.remove(); 10240 } 10241 }); 10242 10243 // Remove expando attributes 10244 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10245 var i = nodes.length; 10246 10247 while (i--) { 10248 nodes[i].attr(name, null); 10249 } 10250 }); 10251 10252 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10253 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10254 var i = nodes.length, node, value; 10255 10256 function trim(value) { 10257 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10258 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10259 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10260 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10261 }; 10262 10263 while (i--) { 10264 node = nodes[i]; 10265 value = node.firstChild ? node.firstChild.value : ''; 10266 10267 if (name === "script") { 10268 // Remove mce- prefix from script elements 10269 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10270 10271 if (value.length > 0) 10272 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10273 } else { 10274 if (value.length > 0) 10275 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10276 } 10277 } 10278 }); 10279 10280 // Convert comments to cdata and handle protected comments 10281 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10282 var i = nodes.length, node; 10283 10284 while (i--) { 10285 node = nodes[i]; 10286 10287 if (node.value.indexOf('[CDATA[') === 0) { 10288 node.name = '#cdata'; 10289 node.type = 4; 10290 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10291 } else if (node.value.indexOf('mce:protected ') === 0) { 10292 node.name = "#text"; 10293 node.type = 3; 10294 node.raw = true; 10295 node.value = unescape(node.value).substr(14); 10296 } 10297 } 10298 }); 10299 10300 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10301 var i = nodes.length, node; 10302 10303 while (i--) { 10304 node = nodes[i]; 10305 if (node.type === 7) 10306 node.remove(); 10307 else if (node.type === 1) { 10308 if (name === "input" && !("type" in node.attributes.map)) 10309 node.attr('type', 'text'); 10310 } 10311 } 10312 }); 10313 10314 // Fix list elements, TODO: Replace this later 10315 if (settings.fix_list_elements) { 10316 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10317 var i = nodes.length, node, parentNode; 10318 10319 while (i--) { 10320 node = nodes[i]; 10321 parentNode = node.parent; 10322 10323 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10324 if (node.prev && node.prev.name === 'li') { 10325 node.prev.append(node); 10326 } 10327 } 10328 } 10329 }); 10330 } 10331 10332 // Remove internal data attributes 10333 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10334 var i = nodes.length; 10335 10336 while (i--) { 10337 nodes[i].attr(name, null); 10338 } 10339 }); 10340 10341 // Return public methods 10342 return { 10343 schema : schema, 10344 10345 addNodeFilter : htmlParser.addNodeFilter, 10346 10347 addAttributeFilter : htmlParser.addAttributeFilter, 10348 10349 onPreProcess : onPreProcess, 10350 10351 onPostProcess : onPostProcess, 10352 10353 serialize : function(node, args) { 10354 var impl, doc, oldDoc, htmlSerializer, content; 10355 10356 // Explorer won't clone contents of script and style and the 10357 // selected index of select elements are cleared on a clone operation. 10358 if (isIE && dom.select('script,style,select,map').length > 0) { 10359 content = node.innerHTML; 10360 node = node.cloneNode(false); 10361 dom.setHTML(node, content); 10362 } else 10363 node = node.cloneNode(true); 10364 10365 // Nodes needs to be attached to something in WebKit/Opera 10366 // Older builds of Opera crashes if you attach the node to an document created dynamically 10367 // and since we can't feature detect a crash we need to sniff the acutal build number 10368 // This fix will make DOM ranges and make Sizzle happy! 10369 impl = node.ownerDocument.implementation; 10370 if (impl.createHTMLDocument) { 10371 // Create an empty HTML document 10372 doc = impl.createHTMLDocument(""); 10373 10374 // Add the element or it's children if it's a body element to the new document 10375 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10376 doc.body.appendChild(doc.importNode(node, true)); 10377 }); 10378 10379 // Grab first child or body element for serialization 10380 if (node.nodeName != 'BODY') 10381 node = doc.body.firstChild; 10382 else 10383 node = doc.body; 10384 10385 // set the new document in DOMUtils so createElement etc works 10386 oldDoc = dom.doc; 10387 dom.doc = doc; 10388 } 10389 10390 args = args || {}; 10391 args.format = args.format || 'html'; 10392 10393 // Pre process 10394 if (!args.no_events) { 10395 args.node = node; 10396 onPreProcess.dispatch(self, args); 10397 } 10398 10399 // Setup serializer 10400 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10401 10402 // Parse and serialize HTML 10403 args.content = htmlSerializer.serialize( 10404 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10405 ); 10406 10407 // Replace all BOM characters for now until we can find a better solution 10408 if (!args.cleanup) 10409 args.content = args.content.replace(/\uFEFF|\u200B/g, ''); 10410 10411 // Post process 10412 if (!args.no_events) 10413 onPostProcess.dispatch(self, args); 10414 10415 // Restore the old document if it was changed 10416 if (oldDoc) 10417 dom.doc = oldDoc; 10418 10419 args.node = null; 10420 10421 return args.content; 10422 }, 10423 10424 addRules : function(rules) { 10425 schema.addValidElements(rules); 10426 }, 10427 10428 setRules : function(rules) { 10429 schema.setValidElements(rules); 10430 } 10431 }; 10432 }; 10433 })(tinymce); 10434 (function(tinymce) { 10435 tinymce.dom.ScriptLoader = function(settings) { 10436 var QUEUED = 0, 10437 LOADING = 1, 10438 LOADED = 2, 10439 states = {}, 10440 queue = [], 10441 scriptLoadedCallbacks = {}, 10442 queueLoadedCallbacks = [], 10443 loading = 0, 10444 undef; 10445 10446 function loadScript(url, callback) { 10447 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10448 10449 // Execute callback when script is loaded 10450 function done() { 10451 dom.remove(id); 10452 10453 if (elm) 10454 elm.onreadystatechange = elm.onload = elm = null; 10455 10456 callback(); 10457 }; 10458 10459 function error() { 10460 // Report the error so it's easier for people to spot loading errors 10461 if (typeof(console) !== "undefined" && console.log) 10462 console.log("Failed to load: " + url); 10463 10464 // We can't mark it as done if there is a load error since 10465 // A) We don't want to produce 404 errors on the server and 10466 // B) the onerror event won't fire on all browsers. 10467 // done(); 10468 }; 10469 10470 id = dom.uniqueId(); 10471 10472 if (tinymce.isIE6) { 10473 uri = new tinymce.util.URI(url); 10474 loc = location; 10475 10476 // If script is from same domain and we 10477 // use IE 6 then use XHR since it's more reliable 10478 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10479 tinymce.util.XHR.send({ 10480 url : tinymce._addVer(uri.getURI()), 10481 success : function(content) { 10482 // Create new temp script element 10483 var script = dom.create('script', { 10484 type : 'text/javascript' 10485 }); 10486 10487 // Evaluate script in global scope 10488 script.text = content; 10489 document.getElementsByTagName('head')[0].appendChild(script); 10490 dom.remove(script); 10491 10492 done(); 10493 }, 10494 10495 error : error 10496 }); 10497 10498 return; 10499 } 10500 } 10501 10502 // Create new script element 10503 elm = document.createElement('script'); 10504 elm.id = id; 10505 elm.type = 'text/javascript'; 10506 elm.src = tinymce._addVer(url); 10507 10508 // Add onload listener for non IE browsers since IE9 10509 // fires onload event before the script is parsed and executed 10510 if (!tinymce.isIE) 10511 elm.onload = done; 10512 10513 // Add onerror event will get fired on some browsers but not all of them 10514 elm.onerror = error; 10515 10516 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10517 if (!tinymce.isOpera) { 10518 elm.onreadystatechange = function() { 10519 var state = elm.readyState; 10520 10521 // Loaded state is passed on IE 6 however there 10522 // are known issues with this method but we can't use 10523 // XHR in a cross domain loading 10524 if (state == 'complete' || state == 'loaded') 10525 done(); 10526 }; 10527 } 10528 10529 // Most browsers support this feature so we report errors 10530 // for those at least to help users track their missing plugins etc 10531 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10532 /*elm.onerror = function() { 10533 alert('Failed to load: ' + url); 10534 };*/ 10535 10536 // Add script to document 10537 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10538 }; 10539 10540 this.isDone = function(url) { 10541 return states[url] == LOADED; 10542 }; 10543 10544 this.markDone = function(url) { 10545 states[url] = LOADED; 10546 }; 10547 10548 this.add = this.load = function(url, callback, scope) { 10549 var item, state = states[url]; 10550 10551 // Add url to load queue 10552 if (state == undef) { 10553 queue.push(url); 10554 states[url] = QUEUED; 10555 } 10556 10557 if (callback) { 10558 // Store away callback for later execution 10559 if (!scriptLoadedCallbacks[url]) 10560 scriptLoadedCallbacks[url] = []; 10561 10562 scriptLoadedCallbacks[url].push({ 10563 func : callback, 10564 scope : scope || this 10565 }); 10566 } 10567 }; 10568 10569 this.loadQueue = function(callback, scope) { 10570 this.loadScripts(queue, callback, scope); 10571 }; 10572 10573 this.loadScripts = function(scripts, callback, scope) { 10574 var loadScripts; 10575 10576 function execScriptLoadedCallbacks(url) { 10577 // Execute URL callback functions 10578 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10579 callback.func.call(callback.scope); 10580 }); 10581 10582 scriptLoadedCallbacks[url] = undef; 10583 }; 10584 10585 queueLoadedCallbacks.push({ 10586 func : callback, 10587 scope : scope || this 10588 }); 10589 10590 loadScripts = function() { 10591 var loadingScripts = tinymce.grep(scripts); 10592 10593 // Current scripts has been handled 10594 scripts.length = 0; 10595 10596 // Load scripts that needs to be loaded 10597 tinymce.each(loadingScripts, function(url) { 10598 // Script is already loaded then execute script callbacks directly 10599 if (states[url] == LOADED) { 10600 execScriptLoadedCallbacks(url); 10601 return; 10602 } 10603 10604 // Is script not loading then start loading it 10605 if (states[url] != LOADING) { 10606 states[url] = LOADING; 10607 loading++; 10608 10609 loadScript(url, function() { 10610 states[url] = LOADED; 10611 loading--; 10612 10613 execScriptLoadedCallbacks(url); 10614 10615 // Load more scripts if they where added by the recently loaded script 10616 loadScripts(); 10617 }); 10618 } 10619 }); 10620 10621 // No scripts are currently loading then execute all pending queue loaded callbacks 10622 if (!loading) { 10623 tinymce.each(queueLoadedCallbacks, function(callback) { 10624 callback.func.call(callback.scope); 10625 }); 10626 10627 queueLoadedCallbacks.length = 0; 10628 } 10629 }; 10630 10631 loadScripts(); 10632 }; 10633 }; 10634 10635 // Global script loader 10636 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10637 })(tinymce); 10638 10639 (function(tinymce) { 10640 tinymce.dom.RangeUtils = function(dom) { 10641 var INVISIBLE_CHAR = '\uFEFF'; 10642 10643 this.walk = function(rng, callback) { 10644 var startContainer = rng.startContainer, 10645 startOffset = rng.startOffset, 10646 endContainer = rng.endContainer, 10647 endOffset = rng.endOffset, 10648 ancestor, startPoint, 10649 endPoint, node, parent, siblings, nodes; 10650 10651 // Handle table cell selection the table plugin enables 10652 // you to fake select table cells and perform formatting actions on them 10653 nodes = dom.select('td.mceSelected,th.mceSelected'); 10654 if (nodes.length > 0) { 10655 tinymce.each(nodes, function(node) { 10656 callback([node]); 10657 }); 10658 10659 return; 10660 } 10661 10662 function exclude(nodes) { 10663 var node; 10664 10665 // First node is excluded 10666 node = nodes[0]; 10667 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10668 nodes.splice(0, 1); 10669 } 10670 10671 // Last node is excluded 10672 node = nodes[nodes.length - 1]; 10673 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 10674 nodes.splice(nodes.length - 1, 1); 10675 } 10676 10677 return nodes; 10678 }; 10679 10680 function collectSiblings(node, name, end_node) { 10681 var siblings = []; 10682 10683 for (; node && node != end_node; node = node[name]) 10684 siblings.push(node); 10685 10686 return siblings; 10687 }; 10688 10689 function findEndPoint(node, root) { 10690 do { 10691 if (node.parentNode == root) 10692 return node; 10693 10694 node = node.parentNode; 10695 } while(node); 10696 }; 10697 10698 function walkBoundary(start_node, end_node, next) { 10699 var siblingName = next ? 'nextSibling' : 'previousSibling'; 10700 10701 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 10702 parent = node.parentNode; 10703 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 10704 10705 if (siblings.length) { 10706 if (!next) 10707 siblings.reverse(); 10708 10709 callback(exclude(siblings)); 10710 } 10711 } 10712 }; 10713 10714 // If index based start position then resolve it 10715 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 10716 startContainer = startContainer.childNodes[startOffset]; 10717 10718 // If index based end position then resolve it 10719 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 10720 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 10721 10722 // Same container 10723 if (startContainer == endContainer) 10724 return callback(exclude([startContainer])); 10725 10726 // Find common ancestor and end points 10727 ancestor = dom.findCommonAncestor(startContainer, endContainer); 10728 10729 // Process left side 10730 for (node = startContainer; node; node = node.parentNode) { 10731 if (node === endContainer) 10732 return walkBoundary(startContainer, ancestor, true); 10733 10734 if (node === ancestor) 10735 break; 10736 } 10737 10738 // Process right side 10739 for (node = endContainer; node; node = node.parentNode) { 10740 if (node === startContainer) 10741 return walkBoundary(endContainer, ancestor); 10742 10743 if (node === ancestor) 10744 break; 10745 } 10746 10747 // Find start/end point 10748 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 10749 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 10750 10751 // Walk left leaf 10752 walkBoundary(startContainer, startPoint, true); 10753 10754 // Walk the middle from start to end point 10755 siblings = collectSiblings( 10756 startPoint == startContainer ? startPoint : startPoint.nextSibling, 10757 'nextSibling', 10758 endPoint == endContainer ? endPoint.nextSibling : endPoint 10759 ); 10760 10761 if (siblings.length) 10762 callback(exclude(siblings)); 10763 10764 // Walk right leaf 10765 walkBoundary(endContainer, endPoint); 10766 }; 10767 10768 this.split = function(rng) { 10769 var startContainer = rng.startContainer, 10770 startOffset = rng.startOffset, 10771 endContainer = rng.endContainer, 10772 endOffset = rng.endOffset; 10773 10774 function splitText(node, offset) { 10775 return node.splitText(offset); 10776 }; 10777 10778 // Handle single text node 10779 if (startContainer == endContainer && startContainer.nodeType == 3) { 10780 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10781 endContainer = splitText(startContainer, startOffset); 10782 startContainer = endContainer.previousSibling; 10783 10784 if (endOffset > startOffset) { 10785 endOffset = endOffset - startOffset; 10786 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 10787 endOffset = endContainer.nodeValue.length; 10788 startOffset = 0; 10789 } else { 10790 endOffset = 0; 10791 } 10792 } 10793 } else { 10794 // Split startContainer text node if needed 10795 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10796 startContainer = splitText(startContainer, startOffset); 10797 startOffset = 0; 10798 } 10799 10800 // Split endContainer text node if needed 10801 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 10802 endContainer = splitText(endContainer, endOffset).previousSibling; 10803 endOffset = endContainer.nodeValue.length; 10804 } 10805 } 10806 10807 return { 10808 startContainer : startContainer, 10809 startOffset : startOffset, 10810 endContainer : endContainer, 10811 endOffset : endOffset 10812 }; 10813 }; 10814 10815 }; 10816 10817 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 10818 if (rng1 && rng2) { 10819 // Compare native IE ranges 10820 if (rng1.item || rng1.duplicate) { 10821 // Both are control ranges and the selected element matches 10822 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 10823 return true; 10824 10825 // Both are text ranges and the range matches 10826 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 10827 return true; 10828 } else { 10829 // Compare w3c ranges 10830 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 10831 } 10832 } 10833 10834 return false; 10835 }; 10836 })(tinymce); 10837 10838 (function(tinymce) { 10839 var Event = tinymce.dom.Event, each = tinymce.each; 10840 10841 tinymce.create('tinymce.ui.KeyboardNavigation', { 10842 KeyboardNavigation: function(settings, dom) { 10843 var t = this, root = settings.root, items = settings.items, 10844 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 10845 excludeFromTabOrder = settings.excludeFromTabOrder, 10846 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 10847 10848 dom = dom || tinymce.DOM; 10849 10850 itemFocussed = function(evt) { 10851 focussedId = evt.target.id; 10852 }; 10853 10854 itemBlurred = function(evt) { 10855 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 10856 }; 10857 10858 rootFocussed = function(evt) { 10859 var item = dom.get(focussedId); 10860 dom.setAttrib(item, 'tabindex', '0'); 10861 item.focus(); 10862 }; 10863 10864 t.focus = function() { 10865 dom.get(focussedId).focus(); 10866 }; 10867 10868 t.destroy = function() { 10869 each(items, function(item) { 10870 var elm = dom.get(item.id); 10871 10872 dom.unbind(elm, 'focus', itemFocussed); 10873 dom.unbind(elm, 'blur', itemBlurred); 10874 }); 10875 10876 var rootElm = dom.get(root); 10877 dom.unbind(rootElm, 'focus', rootFocussed); 10878 dom.unbind(rootElm, 'keydown', rootKeydown); 10879 10880 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 10881 t.destroy = function() {}; 10882 }; 10883 10884 t.moveFocus = function(dir, evt) { 10885 var idx = -1, controls = t.controls, newFocus; 10886 10887 if (!focussedId) 10888 return; 10889 10890 each(items, function(item, index) { 10891 if (item.id === focussedId) { 10892 idx = index; 10893 return false; 10894 } 10895 }); 10896 10897 idx += dir; 10898 if (idx < 0) { 10899 idx = items.length - 1; 10900 } else if (idx >= items.length) { 10901 idx = 0; 10902 } 10903 10904 newFocus = items[idx]; 10905 dom.setAttrib(focussedId, 'tabindex', '-1'); 10906 dom.setAttrib(newFocus.id, 'tabindex', '0'); 10907 dom.get(newFocus.id).focus(); 10908 10909 if (settings.actOnFocus) { 10910 settings.onAction(newFocus.id); 10911 } 10912 10913 if (evt) 10914 Event.cancel(evt); 10915 }; 10916 10917 rootKeydown = function(evt) { 10918 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 10919 10920 switch (evt.keyCode) { 10921 case DOM_VK_LEFT: 10922 if (enableLeftRight) t.moveFocus(-1); 10923 break; 10924 10925 case DOM_VK_RIGHT: 10926 if (enableLeftRight) t.moveFocus(1); 10927 break; 10928 10929 case DOM_VK_UP: 10930 if (enableUpDown) t.moveFocus(-1); 10931 break; 10932 10933 case DOM_VK_DOWN: 10934 if (enableUpDown) t.moveFocus(1); 10935 break; 10936 10937 case DOM_VK_ESCAPE: 10938 if (settings.onCancel) { 10939 settings.onCancel(); 10940 Event.cancel(evt); 10941 } 10942 break; 10943 10944 case DOM_VK_ENTER: 10945 case DOM_VK_RETURN: 10946 case DOM_VK_SPACE: 10947 if (settings.onAction) { 10948 settings.onAction(focussedId); 10949 Event.cancel(evt); 10950 } 10951 break; 10952 } 10953 }; 10954 10955 // Set up state and listeners for each item. 10956 each(items, function(item, idx) { 10957 var tabindex, elm; 10958 10959 if (!item.id) { 10960 item.id = dom.uniqueId('_mce_item_'); 10961 } 10962 10963 elm = dom.get(item.id); 10964 10965 if (excludeFromTabOrder) { 10966 dom.bind(elm, 'blur', itemBlurred); 10967 tabindex = '-1'; 10968 } else { 10969 tabindex = (idx === 0 ? '0' : '-1'); 10970 } 10971 10972 elm.setAttribute('tabindex', tabindex); 10973 dom.bind(elm, 'focus', itemFocussed); 10974 }); 10975 10976 // Setup initial state for root element. 10977 if (items[0]){ 10978 focussedId = items[0].id; 10979 } 10980 10981 dom.setAttrib(root, 'tabindex', '-1'); 10982 10983 // Setup listeners for root element. 10984 var rootElm = dom.get(root); 10985 dom.bind(rootElm, 'focus', rootFocussed); 10986 dom.bind(rootElm, 'keydown', rootKeydown); 10987 } 10988 }); 10989 })(tinymce); 10990 10991 (function(tinymce) { 10992 // Shorten class names 10993 var DOM = tinymce.DOM, is = tinymce.is; 10994 10995 tinymce.create('tinymce.ui.Control', { 10996 Control : function(id, s, editor) { 10997 this.id = id; 10998 this.settings = s = s || {}; 10999 this.rendered = false; 11000 this.onRender = new tinymce.util.Dispatcher(this); 11001 this.classPrefix = ''; 11002 this.scope = s.scope || this; 11003 this.disabled = 0; 11004 this.active = 0; 11005 this.editor = editor; 11006 }, 11007 11008 setAriaProperty : function(property, value) { 11009 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 11010 if (element) { 11011 DOM.setAttrib(element, 'aria-' + property, !!value); 11012 } 11013 }, 11014 11015 focus : function() { 11016 DOM.get(this.id).focus(); 11017 }, 11018 11019 setDisabled : function(s) { 11020 if (s != this.disabled) { 11021 this.setAriaProperty('disabled', s); 11022 11023 this.setState('Disabled', s); 11024 this.setState('Enabled', !s); 11025 this.disabled = s; 11026 } 11027 }, 11028 11029 isDisabled : function() { 11030 return this.disabled; 11031 }, 11032 11033 setActive : function(s) { 11034 if (s != this.active) { 11035 this.setState('Active', s); 11036 this.active = s; 11037 this.setAriaProperty('pressed', s); 11038 } 11039 }, 11040 11041 isActive : function() { 11042 return this.active; 11043 }, 11044 11045 setState : function(c, s) { 11046 var n = DOM.get(this.id); 11047 11048 c = this.classPrefix + c; 11049 11050 if (s) 11051 DOM.addClass(n, c); 11052 else 11053 DOM.removeClass(n, c); 11054 }, 11055 11056 isRendered : function() { 11057 return this.rendered; 11058 }, 11059 11060 renderHTML : function() { 11061 }, 11062 11063 renderTo : function(n) { 11064 DOM.setHTML(n, this.renderHTML()); 11065 }, 11066 11067 postRender : function() { 11068 var t = this, b; 11069 11070 // Set pending states 11071 if (is(t.disabled)) { 11072 b = t.disabled; 11073 t.disabled = -1; 11074 t.setDisabled(b); 11075 } 11076 11077 if (is(t.active)) { 11078 b = t.active; 11079 t.active = -1; 11080 t.setActive(b); 11081 } 11082 }, 11083 11084 remove : function() { 11085 DOM.remove(this.id); 11086 this.destroy(); 11087 }, 11088 11089 destroy : function() { 11090 tinymce.dom.Event.clear(this.id); 11091 } 11092 }); 11093 })(tinymce); 11094 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11095 Container : function(id, s, editor) { 11096 this.parent(id, s, editor); 11097 11098 this.controls = []; 11099 11100 this.lookup = {}; 11101 }, 11102 11103 add : function(c) { 11104 this.lookup[c.id] = c; 11105 this.controls.push(c); 11106 11107 return c; 11108 }, 11109 11110 get : function(n) { 11111 return this.lookup[n]; 11112 } 11113 }); 11114 11115 11116 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11117 Separator : function(id, s) { 11118 this.parent(id, s); 11119 this.classPrefix = 'mceSeparator'; 11120 this.setDisabled(true); 11121 }, 11122 11123 renderHTML : function() { 11124 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11125 } 11126 }); 11127 11128 (function(tinymce) { 11129 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11130 11131 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11132 MenuItem : function(id, s) { 11133 this.parent(id, s); 11134 this.classPrefix = 'mceMenuItem'; 11135 }, 11136 11137 setSelected : function(s) { 11138 this.setState('Selected', s); 11139 this.setAriaProperty('checked', !!s); 11140 this.selected = s; 11141 }, 11142 11143 isSelected : function() { 11144 return this.selected; 11145 }, 11146 11147 postRender : function() { 11148 var t = this; 11149 11150 t.parent(); 11151 11152 // Set pending state 11153 if (is(t.selected)) 11154 t.setSelected(t.selected); 11155 } 11156 }); 11157 })(tinymce); 11158 11159 (function(tinymce) { 11160 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11161 11162 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11163 Menu : function(id, s) { 11164 var t = this; 11165 11166 t.parent(id, s); 11167 t.items = {}; 11168 t.collapsed = false; 11169 t.menuCount = 0; 11170 t.onAddItem = new tinymce.util.Dispatcher(this); 11171 }, 11172 11173 expand : function(d) { 11174 var t = this; 11175 11176 if (d) { 11177 walk(t, function(o) { 11178 if (o.expand) 11179 o.expand(); 11180 }, 'items', t); 11181 } 11182 11183 t.collapsed = false; 11184 }, 11185 11186 collapse : function(d) { 11187 var t = this; 11188 11189 if (d) { 11190 walk(t, function(o) { 11191 if (o.collapse) 11192 o.collapse(); 11193 }, 'items', t); 11194 } 11195 11196 t.collapsed = true; 11197 }, 11198 11199 isCollapsed : function() { 11200 return this.collapsed; 11201 }, 11202 11203 add : function(o) { 11204 if (!o.settings) 11205 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11206 11207 this.onAddItem.dispatch(this, o); 11208 11209 return this.items[o.id] = o; 11210 }, 11211 11212 addSeparator : function() { 11213 return this.add({separator : true}); 11214 }, 11215 11216 addMenu : function(o) { 11217 if (!o.collapse) 11218 o = this.createMenu(o); 11219 11220 this.menuCount++; 11221 11222 return this.add(o); 11223 }, 11224 11225 hasMenus : function() { 11226 return this.menuCount !== 0; 11227 }, 11228 11229 remove : function(o) { 11230 delete this.items[o.id]; 11231 }, 11232 11233 removeAll : function() { 11234 var t = this; 11235 11236 walk(t, function(o) { 11237 if (o.removeAll) 11238 o.removeAll(); 11239 else 11240 o.remove(); 11241 11242 o.destroy(); 11243 }, 'items', t); 11244 11245 t.items = {}; 11246 }, 11247 11248 createMenu : function(o) { 11249 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11250 11251 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11252 11253 return m; 11254 } 11255 }); 11256 })(tinymce); 11257 (function(tinymce) { 11258 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11259 11260 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11261 DropMenu : function(id, s) { 11262 s = s || {}; 11263 s.container = s.container || DOM.doc.body; 11264 s.offset_x = s.offset_x || 0; 11265 s.offset_y = s.offset_y || 0; 11266 s.vp_offset_x = s.vp_offset_x || 0; 11267 s.vp_offset_y = s.vp_offset_y || 0; 11268 11269 if (is(s.icons) && !s.icons) 11270 s['class'] += ' mceNoIcons'; 11271 11272 this.parent(id, s); 11273 this.onShowMenu = new tinymce.util.Dispatcher(this); 11274 this.onHideMenu = new tinymce.util.Dispatcher(this); 11275 this.classPrefix = 'mceMenu'; 11276 }, 11277 11278 createMenu : function(s) { 11279 var t = this, cs = t.settings, m; 11280 11281 s.container = s.container || cs.container; 11282 s.parent = t; 11283 s.constrain = s.constrain || cs.constrain; 11284 s['class'] = s['class'] || cs['class']; 11285 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11286 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11287 s.keyboard_focus = cs.keyboard_focus; 11288 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11289 11290 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11291 11292 return m; 11293 }, 11294 11295 focus : function() { 11296 var t = this; 11297 if (t.keyboardNav) { 11298 t.keyboardNav.focus(); 11299 } 11300 }, 11301 11302 update : function() { 11303 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11304 11305 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11306 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11307 11308 if (!DOM.boxModel) 11309 t.element.setStyles({width : tw + 2, height : th + 2}); 11310 else 11311 t.element.setStyles({width : tw, height : th}); 11312 11313 if (s.max_width) 11314 DOM.setStyle(co, 'width', tw); 11315 11316 if (s.max_height) { 11317 DOM.setStyle(co, 'height', th); 11318 11319 if (tb.clientHeight < s.max_height) 11320 DOM.setStyle(co, 'overflow', 'hidden'); 11321 } 11322 }, 11323 11324 showMenu : function(x, y, px) { 11325 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11326 11327 t.collapse(1); 11328 11329 if (t.isMenuVisible) 11330 return; 11331 11332 if (!t.rendered) { 11333 co = DOM.add(t.settings.container, t.renderNode()); 11334 11335 each(t.items, function(o) { 11336 o.postRender(); 11337 }); 11338 11339 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11340 } else 11341 co = DOM.get('menu_' + t.id); 11342 11343 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11344 if (!tinymce.isOpera) 11345 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11346 11347 DOM.show(co); 11348 t.update(); 11349 11350 x += s.offset_x || 0; 11351 y += s.offset_y || 0; 11352 vp.w -= 4; 11353 vp.h -= 4; 11354 11355 // Move inside viewport if not submenu 11356 if (s.constrain) { 11357 w = co.clientWidth - ot; 11358 h = co.clientHeight - ot; 11359 mx = vp.x + vp.w; 11360 my = vp.y + vp.h; 11361 11362 if ((x + s.vp_offset_x + w) > mx) 11363 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11364 11365 if ((y + s.vp_offset_y + h) > my) 11366 y = Math.max(0, (my - s.vp_offset_y) - h); 11367 } 11368 11369 DOM.setStyles(co, {left : x , top : y}); 11370 t.element.update(); 11371 11372 t.isMenuVisible = 1; 11373 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11374 var m; 11375 11376 e = e.target; 11377 11378 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11379 m = t.items[e.id]; 11380 11381 if (m.isDisabled()) 11382 return; 11383 11384 dm = t; 11385 11386 while (dm) { 11387 if (dm.hideMenu) 11388 dm.hideMenu(); 11389 11390 dm = dm.settings.parent; 11391 } 11392 11393 if (m.settings.onclick) 11394 m.settings.onclick(e); 11395 11396 return false; // Cancel to fix onbeforeunload problem 11397 } 11398 }); 11399 11400 if (t.hasMenus()) { 11401 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11402 var m, r, mi; 11403 11404 e = e.target; 11405 if (e && (e = DOM.getParent(e, 'tr'))) { 11406 m = t.items[e.id]; 11407 11408 if (t.lastMenu) 11409 t.lastMenu.collapse(1); 11410 11411 if (m.isDisabled()) 11412 return; 11413 11414 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11415 //p = DOM.getPos(s.container); 11416 r = DOM.getRect(e); 11417 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11418 t.lastMenu = m; 11419 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11420 } 11421 } 11422 }); 11423 } 11424 11425 Event.add(co, 'keydown', t._keyHandler, t); 11426 11427 t.onShowMenu.dispatch(t); 11428 11429 if (s.keyboard_focus) { 11430 t._setupKeyboardNav(); 11431 } 11432 }, 11433 11434 hideMenu : function(c) { 11435 var t = this, co = DOM.get('menu_' + t.id), e; 11436 11437 if (!t.isMenuVisible) 11438 return; 11439 11440 if (t.keyboardNav) t.keyboardNav.destroy(); 11441 Event.remove(co, 'mouseover', t.mouseOverFunc); 11442 Event.remove(co, 'click', t.mouseClickFunc); 11443 Event.remove(co, 'keydown', t._keyHandler); 11444 DOM.hide(co); 11445 t.isMenuVisible = 0; 11446 11447 if (!c) 11448 t.collapse(1); 11449 11450 if (t.element) 11451 t.element.hide(); 11452 11453 if (e = DOM.get(t.id)) 11454 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11455 11456 t.onHideMenu.dispatch(t); 11457 }, 11458 11459 add : function(o) { 11460 var t = this, co; 11461 11462 o = t.parent(o); 11463 11464 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11465 t._add(DOM.select('tbody', co)[0], o); 11466 11467 return o; 11468 }, 11469 11470 collapse : function(d) { 11471 this.parent(d); 11472 this.hideMenu(1); 11473 }, 11474 11475 remove : function(o) { 11476 DOM.remove(o.id); 11477 this.destroy(); 11478 11479 return this.parent(o); 11480 }, 11481 11482 destroy : function() { 11483 var t = this, co = DOM.get('menu_' + t.id); 11484 11485 if (t.keyboardNav) t.keyboardNav.destroy(); 11486 Event.remove(co, 'mouseover', t.mouseOverFunc); 11487 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11488 Event.remove(co, 'click', t.mouseClickFunc); 11489 Event.remove(co, 'keydown', t._keyHandler); 11490 11491 if (t.element) 11492 t.element.remove(); 11493 11494 DOM.remove(co); 11495 }, 11496 11497 renderNode : function() { 11498 var t = this, s = t.settings, n, tb, co, w; 11499 11500 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); 11501 if (t.settings.parent) { 11502 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11503 } 11504 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11505 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11506 11507 if (s.menu_line) 11508 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11509 11510 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11511 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11512 tb = DOM.add(n, 'tbody'); 11513 11514 each(t.items, function(o) { 11515 t._add(tb, o); 11516 }); 11517 11518 t.rendered = true; 11519 11520 return w; 11521 }, 11522 11523 // Internal functions 11524 _setupKeyboardNav : function(){ 11525 var contextMenu, menuItems, t=this; 11526 contextMenu = DOM.get('menu_' + t.id); 11527 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11528 menuItems.splice(0,0,contextMenu); 11529 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11530 root: 'menu_' + t.id, 11531 items: menuItems, 11532 onCancel: function() { 11533 t.hideMenu(); 11534 }, 11535 enableUpDown: true 11536 }); 11537 contextMenu.focus(); 11538 }, 11539 11540 _keyHandler : function(evt) { 11541 var t = this, e; 11542 switch (evt.keyCode) { 11543 case 37: // Left 11544 if (t.settings.parent) { 11545 t.hideMenu(); 11546 t.settings.parent.focus(); 11547 Event.cancel(evt); 11548 } 11549 break; 11550 case 39: // Right 11551 if (t.mouseOverFunc) 11552 t.mouseOverFunc(evt); 11553 break; 11554 } 11555 }, 11556 11557 _add : function(tb, o) { 11558 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11559 11560 if (s.separator) { 11561 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11562 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11563 11564 if (n = ro.previousSibling) 11565 DOM.addClass(n, 'mceLast'); 11566 11567 return; 11568 } 11569 11570 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11571 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11572 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11573 11574 if (s.parent) { 11575 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11576 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11577 } 11578 11579 DOM.addClass(it, s['class']); 11580 // n = DOM.add(n, 'span', {'class' : 'item'}); 11581 11582 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11583 11584 if (s.icon_src) 11585 DOM.add(ic, 'img', {src : s.icon_src}); 11586 11587 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11588 11589 if (o.settings.style) { 11590 if (typeof o.settings.style == "function") 11591 o.settings.style = o.settings.style(); 11592 11593 DOM.setAttrib(n, 'style', o.settings.style); 11594 } 11595 11596 if (tb.childNodes.length == 1) 11597 DOM.addClass(ro, 'mceFirst'); 11598 11599 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11600 DOM.addClass(ro, 'mceFirst'); 11601 11602 if (o.collapse) 11603 DOM.addClass(ro, cp + 'ItemSub'); 11604 11605 if (n = ro.previousSibling) 11606 DOM.removeClass(n, 'mceLast'); 11607 11608 DOM.addClass(ro, 'mceLast'); 11609 } 11610 }); 11611 })(tinymce); 11612 (function(tinymce) { 11613 var DOM = tinymce.DOM; 11614 11615 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11616 Button : function(id, s, ed) { 11617 this.parent(id, s, ed); 11618 this.classPrefix = 'mceButton'; 11619 }, 11620 11621 renderHTML : function() { 11622 var cp = this.classPrefix, s = this.settings, h, l; 11623 11624 l = DOM.encode(s.label || ''); 11625 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">'; 11626 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11627 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11628 else 11629 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11630 11631 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11632 h += '</a>'; 11633 return h; 11634 }, 11635 11636 postRender : function() { 11637 var t = this, s = t.settings, imgBookmark; 11638 11639 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11640 // need to keep the selection in case the selection is lost 11641 if (tinymce.isIE && t.editor) { 11642 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11643 var nodeName = t.editor.selection.getNode().nodeName; 11644 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11645 }); 11646 } 11647 tinymce.dom.Event.add(t.id, 'click', function(e) { 11648 if (!t.isDisabled()) { 11649 // restore the selection in case the selection is lost in IE 11650 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11651 t.editor.selection.moveToBookmark(imgBookmark); 11652 } 11653 return s.onclick.call(s.scope, e); 11654 } 11655 }); 11656 tinymce.dom.Event.add(t.id, 'keyup', function(e) { 11657 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) 11658 return s.onclick.call(s.scope, e); 11659 }); 11660 } 11661 }); 11662 })(tinymce); 11663 11664 (function(tinymce) { 11665 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11666 11667 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11668 ListBox : function(id, s, ed) { 11669 var t = this; 11670 11671 t.parent(id, s, ed); 11672 11673 t.items = []; 11674 11675 t.onChange = new Dispatcher(t); 11676 11677 t.onPostRender = new Dispatcher(t); 11678 11679 t.onAdd = new Dispatcher(t); 11680 11681 t.onRenderMenu = new tinymce.util.Dispatcher(this); 11682 11683 t.classPrefix = 'mceListBox'; 11684 t.marked = {}; 11685 }, 11686 11687 select : function(va) { 11688 var t = this, fv, f; 11689 11690 t.marked = {}; 11691 11692 if (va == undef) 11693 return t.selectByIndex(-1); 11694 11695 // Is string or number make function selector 11696 if (va && typeof(va)=="function") 11697 f = va; 11698 else { 11699 f = function(v) { 11700 return v == va; 11701 }; 11702 } 11703 11704 // Do we need to do something? 11705 if (va != t.selectedValue) { 11706 // Find item 11707 each(t.items, function(o, i) { 11708 if (f(o.value)) { 11709 fv = 1; 11710 t.selectByIndex(i); 11711 return false; 11712 } 11713 }); 11714 11715 if (!fv) 11716 t.selectByIndex(-1); 11717 } 11718 }, 11719 11720 selectByIndex : function(idx) { 11721 var t = this, e, o, label; 11722 11723 t.marked = {}; 11724 11725 if (idx != t.selectedIndex) { 11726 e = DOM.get(t.id + '_text'); 11727 label = DOM.get(t.id + '_voiceDesc'); 11728 o = t.items[idx]; 11729 11730 if (o) { 11731 t.selectedValue = o.value; 11732 t.selectedIndex = idx; 11733 DOM.setHTML(e, DOM.encode(o.title)); 11734 DOM.setHTML(label, t.settings.title + " - " + o.title); 11735 DOM.removeClass(e, 'mceTitle'); 11736 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 11737 } else { 11738 DOM.setHTML(e, DOM.encode(t.settings.title)); 11739 DOM.setHTML(label, DOM.encode(t.settings.title)); 11740 DOM.addClass(e, 'mceTitle'); 11741 t.selectedValue = t.selectedIndex = null; 11742 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 11743 } 11744 e = 0; 11745 } 11746 }, 11747 11748 mark : function(value) { 11749 this.marked[value] = true; 11750 }, 11751 11752 add : function(n, v, o) { 11753 var t = this; 11754 11755 o = o || {}; 11756 o = tinymce.extend(o, { 11757 title : n, 11758 value : v 11759 }); 11760 11761 t.items.push(o); 11762 t.onAdd.dispatch(t, o); 11763 }, 11764 11765 getLength : function() { 11766 return this.items.length; 11767 }, 11768 11769 renderHTML : function() { 11770 var h = '', t = this, s = t.settings, cp = t.classPrefix; 11771 11772 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 11773 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 11774 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>'; 11775 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>'; 11776 h += '</tr></tbody></table></span>'; 11777 11778 return h; 11779 }, 11780 11781 showMenu : function() { 11782 var t = this, p2, e = DOM.get(this.id), m; 11783 11784 if (t.isDisabled() || t.items.length === 0) 11785 return; 11786 11787 if (t.menu && t.menu.isMenuVisible) 11788 return t.hideMenu(); 11789 11790 if (!t.isMenuRendered) { 11791 t.renderMenu(); 11792 t.isMenuRendered = true; 11793 } 11794 11795 p2 = DOM.getPos(e); 11796 11797 m = t.menu; 11798 m.settings.offset_x = p2.x; 11799 m.settings.offset_y = p2.y; 11800 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 11801 11802 // Select in menu 11803 each(t.items, function(o) { 11804 if (m.items[o.id]) { 11805 m.items[o.id].setSelected(0); 11806 } 11807 }); 11808 11809 each(t.items, function(o) { 11810 if (m.items[o.id] && t.marked[o.value]) { 11811 m.items[o.id].setSelected(1); 11812 } 11813 11814 if (o.value === t.selectedValue) { 11815 m.items[o.id].setSelected(1); 11816 } 11817 }); 11818 11819 m.showMenu(0, e.clientHeight); 11820 11821 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 11822 DOM.addClass(t.id, t.classPrefix + 'Selected'); 11823 11824 //DOM.get(t.id + '_text').focus(); 11825 }, 11826 11827 hideMenu : function(e) { 11828 var t = this; 11829 11830 if (t.menu && t.menu.isMenuVisible) { 11831 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11832 11833 // Prevent double toogles by canceling the mouse click event to the button 11834 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 11835 return; 11836 11837 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 11838 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11839 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 11840 t.menu.hideMenu(); 11841 } 11842 } 11843 }, 11844 11845 renderMenu : function() { 11846 var t = this, m; 11847 11848 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 11849 menu_line : 1, 11850 'class' : t.classPrefix + 'Menu mceNoIcons', 11851 max_width : 250, 11852 max_height : 150 11853 }); 11854 11855 m.onHideMenu.add(function() { 11856 t.hideMenu(); 11857 t.focus(); 11858 }); 11859 11860 m.add({ 11861 title : t.settings.title, 11862 'class' : 'mceMenuItemTitle', 11863 onclick : function() { 11864 if (t.settings.onselect('') !== false) 11865 t.select(''); // Must be runned after 11866 } 11867 }); 11868 11869 each(t.items, function(o) { 11870 // No value then treat it as a title 11871 if (o.value === undef) { 11872 m.add({ 11873 title : o.title, 11874 role : "option", 11875 'class' : 'mceMenuItemTitle', 11876 onclick : function() { 11877 if (t.settings.onselect('') !== false) 11878 t.select(''); // Must be runned after 11879 } 11880 }); 11881 } else { 11882 o.id = DOM.uniqueId(); 11883 o.role= "option"; 11884 o.onclick = function() { 11885 if (t.settings.onselect(o.value) !== false) 11886 t.select(o.value); // Must be runned after 11887 }; 11888 11889 m.add(o); 11890 } 11891 }); 11892 11893 t.onRenderMenu.dispatch(t, m); 11894 t.menu = m; 11895 }, 11896 11897 postRender : function() { 11898 var t = this, cp = t.classPrefix; 11899 11900 Event.add(t.id, 'click', t.showMenu, t); 11901 Event.add(t.id, 'keydown', function(evt) { 11902 if (evt.keyCode == 32) { // Space 11903 t.showMenu(evt); 11904 Event.cancel(evt); 11905 } 11906 }); 11907 Event.add(t.id, 'focus', function() { 11908 if (!t._focused) { 11909 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 11910 if (e.keyCode == 40) { 11911 t.showMenu(); 11912 Event.cancel(e); 11913 } 11914 }); 11915 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 11916 var v; 11917 if (e.keyCode == 13) { 11918 // Fake select on enter 11919 v = t.selectedValue; 11920 t.selectedValue = null; // Needs to be null to fake change 11921 Event.cancel(e); 11922 t.settings.onselect(v); 11923 } 11924 }); 11925 } 11926 11927 t._focused = 1; 11928 }); 11929 Event.add(t.id, 'blur', function() { 11930 Event.remove(t.id, 'keydown', t.keyDownHandler); 11931 Event.remove(t.id, 'keypress', t.keyPressHandler); 11932 t._focused = 0; 11933 }); 11934 11935 // Old IE doesn't have hover on all elements 11936 if (tinymce.isIE6 || !DOM.boxModel) { 11937 Event.add(t.id, 'mouseover', function() { 11938 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11939 DOM.addClass(t.id, cp + 'Hover'); 11940 }); 11941 11942 Event.add(t.id, 'mouseout', function() { 11943 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11944 DOM.removeClass(t.id, cp + 'Hover'); 11945 }); 11946 } 11947 11948 t.onPostRender.dispatch(t, DOM.get(t.id)); 11949 }, 11950 11951 destroy : function() { 11952 this.parent(); 11953 11954 Event.clear(this.id + '_text'); 11955 Event.clear(this.id + '_open'); 11956 } 11957 }); 11958 })(tinymce); 11959 11960 (function(tinymce) { 11961 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11962 11963 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 11964 NativeListBox : function(id, s) { 11965 this.parent(id, s); 11966 this.classPrefix = 'mceNativeListBox'; 11967 }, 11968 11969 setDisabled : function(s) { 11970 DOM.get(this.id).disabled = s; 11971 this.setAriaProperty('disabled', s); 11972 }, 11973 11974 isDisabled : function() { 11975 return DOM.get(this.id).disabled; 11976 }, 11977 11978 select : function(va) { 11979 var t = this, fv, f; 11980 11981 if (va == undef) 11982 return t.selectByIndex(-1); 11983 11984 // Is string or number make function selector 11985 if (va && typeof(va)=="function") 11986 f = va; 11987 else { 11988 f = function(v) { 11989 return v == va; 11990 }; 11991 } 11992 11993 // Do we need to do something? 11994 if (va != t.selectedValue) { 11995 // Find item 11996 each(t.items, function(o, i) { 11997 if (f(o.value)) { 11998 fv = 1; 11999 t.selectByIndex(i); 12000 return false; 12001 } 12002 }); 12003 12004 if (!fv) 12005 t.selectByIndex(-1); 12006 } 12007 }, 12008 12009 selectByIndex : function(idx) { 12010 DOM.get(this.id).selectedIndex = idx + 1; 12011 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 12012 }, 12013 12014 add : function(n, v, a) { 12015 var o, t = this; 12016 12017 a = a || {}; 12018 a.value = v; 12019 12020 if (t.isRendered()) 12021 DOM.add(DOM.get(this.id), 'option', a, n); 12022 12023 o = { 12024 title : n, 12025 value : v, 12026 attribs : a 12027 }; 12028 12029 t.items.push(o); 12030 t.onAdd.dispatch(t, o); 12031 }, 12032 12033 getLength : function() { 12034 return this.items.length; 12035 }, 12036 12037 renderHTML : function() { 12038 var h, t = this; 12039 12040 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12041 12042 each(t.items, function(it) { 12043 h += DOM.createHTML('option', {value : it.value}, it.title); 12044 }); 12045 12046 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12047 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12048 return h; 12049 }, 12050 12051 postRender : function() { 12052 var t = this, ch, changeListenerAdded = true; 12053 12054 t.rendered = true; 12055 12056 function onChange(e) { 12057 var v = t.items[e.target.selectedIndex - 1]; 12058 12059 if (v && (v = v.value)) { 12060 t.onChange.dispatch(t, v); 12061 12062 if (t.settings.onselect) 12063 t.settings.onselect(v); 12064 } 12065 }; 12066 12067 Event.add(t.id, 'change', onChange); 12068 12069 // Accessibility keyhandler 12070 Event.add(t.id, 'keydown', function(e) { 12071 var bf; 12072 12073 Event.remove(t.id, 'change', ch); 12074 changeListenerAdded = false; 12075 12076 bf = Event.add(t.id, 'blur', function() { 12077 if (changeListenerAdded) return; 12078 changeListenerAdded = true; 12079 Event.add(t.id, 'change', onChange); 12080 Event.remove(t.id, 'blur', bf); 12081 }); 12082 12083 //prevent default left and right keys on chrome - so that the keyboard navigation is used. 12084 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { 12085 return Event.prevent(e); 12086 } 12087 12088 if (e.keyCode == 13 || e.keyCode == 32) { 12089 onChange(e); 12090 return Event.cancel(e); 12091 } 12092 }); 12093 12094 t.onPostRender.dispatch(t, DOM.get(t.id)); 12095 } 12096 }); 12097 })(tinymce); 12098 12099 (function(tinymce) { 12100 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12101 12102 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12103 MenuButton : function(id, s, ed) { 12104 this.parent(id, s, ed); 12105 12106 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12107 12108 s.menu_container = s.menu_container || DOM.doc.body; 12109 }, 12110 12111 showMenu : function() { 12112 var t = this, p1, p2, e = DOM.get(t.id), m; 12113 12114 if (t.isDisabled()) 12115 return; 12116 12117 if (!t.isMenuRendered) { 12118 t.renderMenu(); 12119 t.isMenuRendered = true; 12120 } 12121 12122 if (t.isMenuVisible) 12123 return t.hideMenu(); 12124 12125 p1 = DOM.getPos(t.settings.menu_container); 12126 p2 = DOM.getPos(e); 12127 12128 m = t.menu; 12129 m.settings.offset_x = p2.x; 12130 m.settings.offset_y = p2.y; 12131 m.settings.vp_offset_x = p2.x; 12132 m.settings.vp_offset_y = p2.y; 12133 m.settings.keyboard_focus = t._focused; 12134 m.showMenu(0, e.firstChild.clientHeight); 12135 12136 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12137 t.setState('Selected', 1); 12138 12139 t.isMenuVisible = 1; 12140 }, 12141 12142 renderMenu : function() { 12143 var t = this, m; 12144 12145 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12146 menu_line : 1, 12147 'class' : this.classPrefix + 'Menu', 12148 icons : t.settings.icons 12149 }); 12150 12151 m.onHideMenu.add(function() { 12152 t.hideMenu(); 12153 t.focus(); 12154 }); 12155 12156 t.onRenderMenu.dispatch(t, m); 12157 t.menu = m; 12158 }, 12159 12160 hideMenu : function(e) { 12161 var t = this; 12162 12163 // Prevent double toogles by canceling the mouse click event to the button 12164 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12165 return; 12166 12167 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12168 t.setState('Selected', 0); 12169 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12170 if (t.menu) 12171 t.menu.hideMenu(); 12172 } 12173 12174 t.isMenuVisible = 0; 12175 }, 12176 12177 postRender : function() { 12178 var t = this, s = t.settings; 12179 12180 Event.add(t.id, 'click', function() { 12181 if (!t.isDisabled()) { 12182 if (s.onclick) 12183 s.onclick(t.value); 12184 12185 t.showMenu(); 12186 } 12187 }); 12188 } 12189 }); 12190 })(tinymce); 12191 12192 (function(tinymce) { 12193 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12194 12195 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12196 SplitButton : function(id, s, ed) { 12197 this.parent(id, s, ed); 12198 this.classPrefix = 'mceSplitButton'; 12199 }, 12200 12201 renderHTML : function() { 12202 var h, t = this, s = t.settings, h1; 12203 12204 h = '<tbody><tr>'; 12205 12206 if (s.image) 12207 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12208 else 12209 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12210 12211 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12212 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12213 12214 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12215 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12216 12217 h += '</tr></tbody>'; 12218 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12219 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12220 }, 12221 12222 postRender : function() { 12223 var t = this, s = t.settings, activate; 12224 12225 if (s.onclick) { 12226 activate = function(evt) { 12227 if (!t.isDisabled()) { 12228 s.onclick(t.value); 12229 Event.cancel(evt); 12230 } 12231 }; 12232 Event.add(t.id + '_action', 'click', activate); 12233 Event.add(t.id, ['click', 'keydown'], function(evt) { 12234 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12235 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12236 activate(); 12237 Event.cancel(evt); 12238 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12239 t.showMenu(); 12240 Event.cancel(evt); 12241 } 12242 }); 12243 } 12244 12245 Event.add(t.id + '_open', 'click', function (evt) { 12246 t.showMenu(); 12247 Event.cancel(evt); 12248 }); 12249 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12250 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12251 12252 // Old IE doesn't have hover on all elements 12253 if (tinymce.isIE6 || !DOM.boxModel) { 12254 Event.add(t.id, 'mouseover', function() { 12255 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12256 DOM.addClass(t.id, 'mceSplitButtonHover'); 12257 }); 12258 12259 Event.add(t.id, 'mouseout', function() { 12260 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12261 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12262 }); 12263 } 12264 }, 12265 12266 destroy : function() { 12267 this.parent(); 12268 12269 Event.clear(this.id + '_action'); 12270 Event.clear(this.id + '_open'); 12271 Event.clear(this.id); 12272 } 12273 }); 12274 })(tinymce); 12275 12276 (function(tinymce) { 12277 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12278 12279 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12280 ColorSplitButton : function(id, s, ed) { 12281 var t = this; 12282 12283 t.parent(id, s, ed); 12284 12285 t.settings = s = tinymce.extend({ 12286 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', 12287 grid_width : 8, 12288 default_color : '#888888' 12289 }, t.settings); 12290 12291 t.onShowMenu = new tinymce.util.Dispatcher(t); 12292 12293 t.onHideMenu = new tinymce.util.Dispatcher(t); 12294 12295 t.value = s.default_color; 12296 }, 12297 12298 showMenu : function() { 12299 var t = this, r, p, e, p2; 12300 12301 if (t.isDisabled()) 12302 return; 12303 12304 if (!t.isMenuRendered) { 12305 t.renderMenu(); 12306 t.isMenuRendered = true; 12307 } 12308 12309 if (t.isMenuVisible) 12310 return t.hideMenu(); 12311 12312 e = DOM.get(t.id); 12313 DOM.show(t.id + '_menu'); 12314 DOM.addClass(e, 'mceSplitButtonSelected'); 12315 p2 = DOM.getPos(e); 12316 DOM.setStyles(t.id + '_menu', { 12317 left : p2.x, 12318 top : p2.y + e.firstChild.clientHeight, 12319 zIndex : 200000 12320 }); 12321 e = 0; 12322 12323 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12324 t.onShowMenu.dispatch(t); 12325 12326 if (t._focused) { 12327 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12328 if (e.keyCode == 27) 12329 t.hideMenu(); 12330 }); 12331 12332 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12333 } 12334 12335 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12336 root: t.id + '_menu', 12337 items: DOM.select('a', t.id + '_menu'), 12338 onCancel: function() { 12339 t.hideMenu(); 12340 t.focus(); 12341 } 12342 }); 12343 12344 t.keyboardNav.focus(); 12345 t.isMenuVisible = 1; 12346 }, 12347 12348 hideMenu : function(e) { 12349 var t = this; 12350 12351 if (t.isMenuVisible) { 12352 // Prevent double toogles by canceling the mouse click event to the button 12353 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12354 return; 12355 12356 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12357 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12358 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12359 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12360 DOM.hide(t.id + '_menu'); 12361 } 12362 12363 t.isMenuVisible = 0; 12364 t.onHideMenu.dispatch(); 12365 t.keyboardNav.destroy(); 12366 } 12367 }, 12368 12369 renderMenu : function() { 12370 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12371 12372 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); 12373 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12374 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12375 12376 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12377 tb = DOM.add(n, 'tbody'); 12378 12379 // Generate color grid 12380 i = 0; 12381 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12382 c = c.replace(/^#/, ''); 12383 12384 if (!i--) { 12385 tr = DOM.add(tb, 'tr'); 12386 i = s.grid_width - 1; 12387 } 12388 12389 n = DOM.add(tr, 'td'); 12390 var settings = { 12391 href : 'javascript:;', 12392 style : { 12393 backgroundColor : '#' + c 12394 }, 12395 'title': t.editor.getLang('colors.' + c, c), 12396 'data-mce-color' : '#' + c 12397 }; 12398 12399 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12400 if (!tinymce.isIE ) { 12401 settings.role = 'option'; 12402 } 12403 12404 n = DOM.add(n, 'a', settings); 12405 12406 if (t.editor.forcedHighContrastMode) { 12407 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12408 if (n.getContext && (context = n.getContext("2d"))) { 12409 context.fillStyle = '#' + c; 12410 context.fillRect(0, 0, 16, 16); 12411 } else { 12412 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12413 DOM.remove(n); 12414 } 12415 } 12416 }); 12417 12418 if (s.more_colors_func) { 12419 n = DOM.add(tb, 'tr'); 12420 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12421 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12422 12423 Event.add(n, 'click', function(e) { 12424 s.more_colors_func.call(s.more_colors_scope || this); 12425 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12426 }); 12427 } 12428 12429 DOM.addClass(m, 'mceColorSplitMenu'); 12430 12431 // Prevent IE from scrolling and hindering click to occur #4019 12432 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12433 12434 Event.add(t.id + '_menu', 'click', function(e) { 12435 var c; 12436 12437 e = DOM.getParent(e.target, 'a', tb); 12438 12439 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12440 t.setColor(c); 12441 12442 return false; // Prevent IE auto save warning 12443 }); 12444 12445 return w; 12446 }, 12447 12448 setColor : function(c) { 12449 this.displayColor(c); 12450 this.hideMenu(); 12451 this.settings.onselect(c); 12452 }, 12453 12454 displayColor : function(c) { 12455 var t = this; 12456 12457 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12458 12459 t.value = c; 12460 }, 12461 12462 postRender : function() { 12463 var t = this, id = t.id; 12464 12465 t.parent(); 12466 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12467 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12468 }, 12469 12470 destroy : function() { 12471 var self = this; 12472 12473 self.parent(); 12474 12475 Event.clear(self.id + '_menu'); 12476 Event.clear(self.id + '_more'); 12477 DOM.remove(self.id + '_menu'); 12478 12479 if (self.keyboardNav) { 12480 self.keyboardNav.destroy(); 12481 } 12482 } 12483 }); 12484 })(tinymce); 12485 12486 (function(tinymce) { 12487 // Shorten class names 12488 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12489 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12490 renderHTML : function() { 12491 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12492 12493 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12494 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12495 h.push("<span role='application'>"); 12496 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12497 each(controls, function(toolbar) { 12498 h.push(toolbar.renderHTML()); 12499 }); 12500 h.push("</span>"); 12501 h.push('</div>'); 12502 12503 return h.join(''); 12504 }, 12505 12506 focus : function() { 12507 var t = this; 12508 dom.get(t.id).focus(); 12509 }, 12510 12511 postRender : function() { 12512 var t = this, items = []; 12513 12514 each(t.controls, function(toolbar) { 12515 each (toolbar.controls, function(control) { 12516 if (control.id) { 12517 items.push(control); 12518 } 12519 }); 12520 }); 12521 12522 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12523 root: t.id, 12524 items: items, 12525 onCancel: function() { 12526 //Move focus if webkit so that navigation back will read the item. 12527 if (tinymce.isWebKit) { 12528 dom.get(t.editor.id+"_ifr").focus(); 12529 } 12530 t.editor.focus(); 12531 }, 12532 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12533 }); 12534 }, 12535 12536 destroy : function() { 12537 var self = this; 12538 12539 self.parent(); 12540 self.keyNav.destroy(); 12541 Event.clear(self.id); 12542 } 12543 }); 12544 })(tinymce); 12545 12546 (function(tinymce) { 12547 // Shorten class names 12548 var dom = tinymce.DOM, each = tinymce.each; 12549 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12550 renderHTML : function() { 12551 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12552 12553 cl = t.controls; 12554 for (i=0; i<cl.length; i++) { 12555 // Get current control, prev control, next control and if the control is a list box or not 12556 co = cl[i]; 12557 pr = cl[i - 1]; 12558 nx = cl[i + 1]; 12559 12560 // Add toolbar start 12561 if (i === 0) { 12562 c = 'mceToolbarStart'; 12563 12564 if (co.Button) 12565 c += ' mceToolbarStartButton'; 12566 else if (co.SplitButton) 12567 c += ' mceToolbarStartSplitButton'; 12568 else if (co.ListBox) 12569 c += ' mceToolbarStartListBox'; 12570 12571 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12572 } 12573 12574 // Add toolbar end before list box and after the previous button 12575 // This is to fix the o2k7 editor skins 12576 if (pr && co.ListBox) { 12577 if (pr.Button || pr.SplitButton) 12578 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12579 } 12580 12581 // Render control HTML 12582 12583 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12584 if (dom.stdMode) 12585 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12586 else 12587 h += '<td>' + co.renderHTML() + '</td>'; 12588 12589 // Add toolbar start after list box and before the next button 12590 // This is to fix the o2k7 editor skins 12591 if (nx && co.ListBox) { 12592 if (nx.Button || nx.SplitButton) 12593 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12594 } 12595 } 12596 12597 c = 'mceToolbarEnd'; 12598 12599 if (co.Button) 12600 c += ' mceToolbarEndButton'; 12601 else if (co.SplitButton) 12602 c += ' mceToolbarEndSplitButton'; 12603 else if (co.ListBox) 12604 c += ' mceToolbarEndListBox'; 12605 12606 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12607 12608 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>'); 12609 } 12610 }); 12611 })(tinymce); 12612 12613 (function(tinymce) { 12614 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12615 12616 tinymce.create('tinymce.AddOnManager', { 12617 AddOnManager : function() { 12618 var self = this; 12619 12620 self.items = []; 12621 self.urls = {}; 12622 self.lookup = {}; 12623 self.onAdd = new Dispatcher(self); 12624 }, 12625 12626 get : function(n) { 12627 if (this.lookup[n]) { 12628 return this.lookup[n].instance; 12629 } else { 12630 return undefined; 12631 } 12632 }, 12633 12634 dependencies : function(n) { 12635 var result; 12636 if (this.lookup[n]) { 12637 result = this.lookup[n].dependencies; 12638 } 12639 return result || []; 12640 }, 12641 12642 requireLangPack : function(n) { 12643 var s = tinymce.settings; 12644 12645 if (s && s.language && s.language_load !== false) 12646 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12647 }, 12648 12649 add : function(id, o, dependencies) { 12650 this.items.push(o); 12651 this.lookup[id] = {instance:o, dependencies:dependencies}; 12652 this.onAdd.dispatch(this, id, o); 12653 12654 return o; 12655 }, 12656 createUrl: function(baseUrl, dep) { 12657 if (typeof dep === "object") { 12658 return dep 12659 } else { 12660 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12661 } 12662 }, 12663 12664 addComponents: function(pluginName, scripts) { 12665 var pluginUrl = this.urls[pluginName]; 12666 tinymce.each(scripts, function(script){ 12667 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12668 }); 12669 }, 12670 12671 load : function(n, u, cb, s) { 12672 var t = this, url = u; 12673 12674 function loadDependencies() { 12675 var dependencies = t.dependencies(n); 12676 tinymce.each(dependencies, function(dep) { 12677 var newUrl = t.createUrl(u, dep); 12678 t.load(newUrl.resource, newUrl, undefined, undefined); 12679 }); 12680 if (cb) { 12681 if (s) { 12682 cb.call(s); 12683 } else { 12684 cb.call(tinymce.ScriptLoader); 12685 } 12686 } 12687 } 12688 12689 if (t.urls[n]) 12690 return; 12691 if (typeof u === "object") 12692 url = u.prefix + u.resource + u.suffix; 12693 12694 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 12695 url = tinymce.baseURL + '/' + url; 12696 12697 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 12698 12699 if (t.lookup[n]) { 12700 loadDependencies(); 12701 } else { 12702 tinymce.ScriptLoader.add(url, loadDependencies, s); 12703 } 12704 } 12705 }); 12706 12707 // Create plugin and theme managers 12708 tinymce.PluginManager = new tinymce.AddOnManager(); 12709 tinymce.ThemeManager = new tinymce.AddOnManager(); 12710 }(tinymce)); 12711 12712 (function(tinymce) { 12713 // Shorten names 12714 var each = tinymce.each, extend = tinymce.extend, 12715 DOM = tinymce.DOM, Event = tinymce.dom.Event, 12716 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 12717 explode = tinymce.explode, 12718 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 12719 12720 // Setup some URLs where the editor API is located and where the document is 12721 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 12722 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 12723 tinymce.documentBaseURL += '/'; 12724 12725 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 12726 12727 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 12728 12729 // Add before unload listener 12730 // This was required since IE was leaking memory if you added and removed beforeunload listeners 12731 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 12732 tinymce.onBeforeUnload = new Dispatcher(tinymce); 12733 12734 // Must be on window or IE will leak if the editor is placed in frame or iframe 12735 Event.add(window, 'beforeunload', function(e) { 12736 tinymce.onBeforeUnload.dispatch(tinymce, e); 12737 }); 12738 12739 tinymce.onAddEditor = new Dispatcher(tinymce); 12740 12741 tinymce.onRemoveEditor = new Dispatcher(tinymce); 12742 12743 tinymce.EditorManager = extend(tinymce, { 12744 editors : [], 12745 12746 i18n : {}, 12747 12748 activeEditor : null, 12749 12750 init : function(s) { 12751 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 12752 12753 function createId(elm) { 12754 var id = elm.id; 12755 12756 // Use element id, or unique name or generate a unique id 12757 if (!id) { 12758 id = elm.name; 12759 12760 if (id && !DOM.get(id)) { 12761 id = elm.name; 12762 } else { 12763 // Generate unique name 12764 id = DOM.uniqueId(); 12765 } 12766 12767 elm.setAttribute('id', id); 12768 } 12769 12770 return id; 12771 }; 12772 12773 function execCallback(se, n, s) { 12774 var f = se[n]; 12775 12776 if (!f) 12777 return; 12778 12779 if (tinymce.is(f, 'string')) { 12780 s = f.replace(/\.\w+$/, ''); 12781 s = s ? tinymce.resolve(s) : 0; 12782 f = tinymce.resolve(f); 12783 } 12784 12785 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 12786 }; 12787 12788 function hasClass(n, c) { 12789 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 12790 }; 12791 12792 t.settings = s; 12793 12794 // Legacy call 12795 Event.bind(window, 'ready', function() { 12796 var l, co; 12797 12798 execCallback(s, 'onpageload'); 12799 12800 switch (s.mode) { 12801 case "exact": 12802 l = s.elements || ''; 12803 12804 if(l.length > 0) { 12805 each(explode(l), function(v) { 12806 if (DOM.get(v)) { 12807 ed = new tinymce.Editor(v, s); 12808 el.push(ed); 12809 ed.render(1); 12810 } else { 12811 each(document.forms, function(f) { 12812 each(f.elements, function(e) { 12813 if (e.name === v) { 12814 v = 'mce_editor_' + instanceCounter++; 12815 DOM.setAttrib(e, 'id', v); 12816 12817 ed = new tinymce.Editor(v, s); 12818 el.push(ed); 12819 ed.render(1); 12820 } 12821 }); 12822 }); 12823 } 12824 }); 12825 } 12826 break; 12827 12828 case "textareas": 12829 case "specific_textareas": 12830 each(DOM.select('textarea'), function(elm) { 12831 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 12832 return; 12833 12834 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 12835 ed = new tinymce.Editor(createId(elm), s); 12836 el.push(ed); 12837 ed.render(1); 12838 } 12839 }); 12840 break; 12841 12842 default: 12843 if (s.types) { 12844 // Process type specific selector 12845 each(s.types, function(type) { 12846 each(DOM.select(type.selector), function(elm) { 12847 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 12848 el.push(editor); 12849 editor.render(1); 12850 }); 12851 }); 12852 } else if (s.selector) { 12853 // Process global selector 12854 each(DOM.select(s.selector), function(elm) { 12855 var editor = new tinymce.Editor(createId(elm), s); 12856 el.push(editor); 12857 editor.render(1); 12858 }); 12859 } 12860 } 12861 12862 // Call onInit when all editors are initialized 12863 if (s.oninit) { 12864 l = co = 0; 12865 12866 each(el, function(ed) { 12867 co++; 12868 12869 if (!ed.initialized) { 12870 // Wait for it 12871 ed.onInit.add(function() { 12872 l++; 12873 12874 // All done 12875 if (l == co) 12876 execCallback(s, 'oninit'); 12877 }); 12878 } else 12879 l++; 12880 12881 // All done 12882 if (l == co) 12883 execCallback(s, 'oninit'); 12884 }); 12885 } 12886 }); 12887 }, 12888 12889 get : function(id) { 12890 if (id === undef) 12891 return this.editors; 12892 12893 return this.editors[id]; 12894 }, 12895 12896 getInstanceById : function(id) { 12897 return this.get(id); 12898 }, 12899 12900 add : function(editor) { 12901 var self = this, editors = self.editors; 12902 12903 // Add named and index editor instance 12904 editors[editor.id] = editor; 12905 editors.push(editor); 12906 12907 self._setActive(editor); 12908 self.onAddEditor.dispatch(self, editor); 12909 12910 12911 return editor; 12912 }, 12913 12914 remove : function(editor) { 12915 var t = this, i, editors = t.editors; 12916 12917 // Not in the collection 12918 if (!editors[editor.id]) 12919 return null; 12920 12921 delete editors[editor.id]; 12922 12923 for (i = 0; i < editors.length; i++) { 12924 if (editors[i] == editor) { 12925 editors.splice(i, 1); 12926 break; 12927 } 12928 } 12929 12930 // Select another editor since the active one was removed 12931 if (t.activeEditor == editor) 12932 t._setActive(editors[0]); 12933 12934 editor.destroy(); 12935 t.onRemoveEditor.dispatch(t, editor); 12936 12937 return editor; 12938 }, 12939 12940 execCommand : function(c, u, v) { 12941 var t = this, ed = t.get(v), w; 12942 12943 function clr() { 12944 ed.destroy(); 12945 w.detachEvent('onunload', clr); 12946 w = w.tinyMCE = w.tinymce = null; // IE leak 12947 }; 12948 12949 // Manager commands 12950 switch (c) { 12951 case "mceFocus": 12952 ed.focus(); 12953 return true; 12954 12955 case "mceAddEditor": 12956 case "mceAddControl": 12957 if (!t.get(v)) 12958 new tinymce.Editor(v, t.settings).render(); 12959 12960 return true; 12961 12962 case "mceAddFrameControl": 12963 w = v.window; 12964 12965 // Add tinyMCE global instance and tinymce namespace to specified window 12966 w.tinyMCE = tinyMCE; 12967 w.tinymce = tinymce; 12968 12969 tinymce.DOM.doc = w.document; 12970 tinymce.DOM.win = w; 12971 12972 ed = new tinymce.Editor(v.element_id, v); 12973 ed.render(); 12974 12975 // Fix IE memory leaks 12976 if (tinymce.isIE) { 12977 w.attachEvent('onunload', clr); 12978 } 12979 12980 v.page_window = null; 12981 12982 return true; 12983 12984 case "mceRemoveEditor": 12985 case "mceRemoveControl": 12986 if (ed) 12987 ed.remove(); 12988 12989 return true; 12990 12991 case 'mceToggleEditor': 12992 if (!ed) { 12993 t.execCommand('mceAddControl', 0, v); 12994 return true; 12995 } 12996 12997 if (ed.isHidden()) 12998 ed.show(); 12999 else 13000 ed.hide(); 13001 13002 return true; 13003 } 13004 13005 // Run command on active editor 13006 if (t.activeEditor) 13007 return t.activeEditor.execCommand(c, u, v); 13008 13009 return false; 13010 }, 13011 13012 execInstanceCommand : function(id, c, u, v) { 13013 var ed = this.get(id); 13014 13015 if (ed) 13016 return ed.execCommand(c, u, v); 13017 13018 return false; 13019 }, 13020 13021 triggerSave : function() { 13022 each(this.editors, function(e) { 13023 e.save(); 13024 }); 13025 }, 13026 13027 addI18n : function(p, o) { 13028 var lo, i18n = this.i18n; 13029 13030 if (!tinymce.is(p, 'string')) { 13031 each(p, function(o, lc) { 13032 each(o, function(o, g) { 13033 each(o, function(o, k) { 13034 if (g === 'common') 13035 i18n[lc + '.' + k] = o; 13036 else 13037 i18n[lc + '.' + g + '.' + k] = o; 13038 }); 13039 }); 13040 }); 13041 } else { 13042 each(o, function(o, k) { 13043 i18n[p + '.' + k] = o; 13044 }); 13045 } 13046 }, 13047 13048 // Private methods 13049 13050 _setActive : function(editor) { 13051 this.selectedInstance = this.activeEditor = editor; 13052 } 13053 }); 13054 })(tinymce); 13055 13056 (function(tinymce) { 13057 // Shorten these names 13058 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13059 each = tinymce.each, isGecko = tinymce.isGecko, 13060 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13061 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13062 explode = tinymce.explode; 13063 13064 tinymce.create('tinymce.Editor', { 13065 Editor : function(id, settings) { 13066 var self = this, TRUE = true; 13067 13068 self.settings = settings = extend({ 13069 id : id, 13070 language : 'en', 13071 theme : 'advanced', 13072 skin : 'default', 13073 delta_width : 0, 13074 delta_height : 0, 13075 popup_css : '', 13076 plugins : '', 13077 document_base_url : tinymce.documentBaseURL, 13078 add_form_submit_trigger : TRUE, 13079 submit_patch : TRUE, 13080 add_unload_trigger : TRUE, 13081 convert_urls : TRUE, 13082 relative_urls : TRUE, 13083 remove_script_host : TRUE, 13084 table_inline_editing : false, 13085 object_resizing : TRUE, 13086 accessibility_focus : TRUE, 13087 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13088 visual : TRUE, 13089 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13090 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13091 apply_source_formatting : TRUE, 13092 directionality : 'ltr', 13093 forced_root_block : 'p', 13094 hidden_input : TRUE, 13095 padd_empty_editor : TRUE, 13096 render_ui : TRUE, 13097 indentation : '30px', 13098 fix_table_elements : TRUE, 13099 inline_styles : TRUE, 13100 convert_fonts_to_spans : TRUE, 13101 indent : 'simple', 13102 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13103 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13104 validate : TRUE, 13105 entity_encoding : 'named', 13106 url_converter : self.convertURL, 13107 url_converter_scope : self, 13108 ie7_compat : TRUE 13109 }, settings); 13110 13111 self.id = self.editorId = id; 13112 13113 self.isNotDirty = false; 13114 13115 self.plugins = {}; 13116 13117 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13118 base_uri : tinyMCE.baseURI 13119 }); 13120 13121 self.baseURI = tinymce.baseURI; 13122 13123 self.contentCSS = []; 13124 13125 self.contentStyles = []; 13126 13127 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13128 self.setupEvents(); 13129 13130 // Internal command handler objects 13131 self.execCommands = {}; 13132 self.queryStateCommands = {}; 13133 self.queryValueCommands = {}; 13134 13135 // Call setup 13136 self.execCallback('setup', self); 13137 }, 13138 13139 render : function(nst) { 13140 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13141 13142 // Page is not loaded yet, wait for it 13143 if (!Event.domLoaded) { 13144 Event.add(window, 'ready', function() { 13145 t.render(); 13146 }); 13147 return; 13148 } 13149 13150 tinyMCE.settings = s; 13151 13152 // Element not found, then skip initialization 13153 if (!t.getElement()) 13154 return; 13155 13156 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13157 // here since the browser says it has contentEditable support but there is no visible caret. 13158 if (tinymce.isIDevice && !tinymce.isIOS5) 13159 return; 13160 13161 // Add hidden input for non input elements inside form elements 13162 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13163 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13164 13165 // Hide target element early to prevent content flashing 13166 if (!s.content_editable) { 13167 t.orgVisibility = t.getElement().style.visibility; 13168 t.getElement().style.visibility = 'hidden'; 13169 } 13170 13171 if (tinymce.WindowManager) 13172 t.windowManager = new tinymce.WindowManager(t); 13173 13174 if (s.encoding == 'xml') { 13175 t.onGetContent.add(function(ed, o) { 13176 if (o.save) 13177 o.content = DOM.encode(o.content); 13178 }); 13179 } 13180 13181 if (s.add_form_submit_trigger) { 13182 t.onSubmit.addToTop(function() { 13183 if (t.initialized) { 13184 t.save(); 13185 t.isNotDirty = 1; 13186 } 13187 }); 13188 } 13189 13190 if (s.add_unload_trigger) { 13191 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13192 if (t.initialized && !t.destroyed && !t.isHidden()) 13193 t.save({format : 'raw', no_events : true}); 13194 }); 13195 } 13196 13197 tinymce.addUnload(t.destroy, t); 13198 13199 if (s.submit_patch) { 13200 t.onBeforeRenderUI.add(function() { 13201 var n = t.getElement().form; 13202 13203 if (!n) 13204 return; 13205 13206 // Already patched 13207 if (n._mceOldSubmit) 13208 return; 13209 13210 // Check page uses id="submit" or name="submit" for it's submit button 13211 if (!n.submit.nodeType && !n.submit.length) { 13212 t.formElement = n; 13213 n._mceOldSubmit = n.submit; 13214 n.submit = function() { 13215 // Save all instances 13216 tinymce.triggerSave(); 13217 t.isNotDirty = 1; 13218 13219 return t.formElement._mceOldSubmit(t.formElement); 13220 }; 13221 } 13222 13223 n = null; 13224 }); 13225 } 13226 13227 // Load scripts 13228 function loadScripts() { 13229 if (s.language && s.language_load !== false) 13230 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13231 13232 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13233 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13234 13235 each(explode(s.plugins), function(p) { 13236 if (p &&!PluginManager.urls[p]) { 13237 if (p.charAt(0) == '-') { 13238 p = p.substr(1, p.length); 13239 var dependencies = PluginManager.dependencies(p); 13240 each(dependencies, function(dep) { 13241 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13242 dep = PluginManager.createUrl(defaultSettings, dep); 13243 PluginManager.load(dep.resource, dep); 13244 }); 13245 } else { 13246 // Skip safari plugin, since it is removed as of 3.3b1 13247 if (p == 'safari') { 13248 return; 13249 } 13250 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13251 } 13252 } 13253 }); 13254 13255 // Init when que is loaded 13256 sl.loadQueue(function() { 13257 if (!t.removed) 13258 t.init(); 13259 }); 13260 }; 13261 13262 loadScripts(); 13263 }, 13264 13265 init : function() { 13266 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13267 13268 tinymce.add(t); 13269 13270 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13271 13272 if (s.theme) { 13273 if (typeof s.theme != "function") { 13274 s.theme = s.theme.replace(/-/, ''); 13275 o = ThemeManager.get(s.theme); 13276 t.theme = new o(); 13277 13278 if (t.theme.init) 13279 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13280 } else { 13281 t.theme = s.theme; 13282 } 13283 } 13284 13285 function initPlugin(p) { 13286 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13287 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13288 each(PluginManager.dependencies(p), function(dep){ 13289 initPlugin(dep); 13290 }); 13291 po = new c(t, u); 13292 13293 t.plugins[p] = po; 13294 13295 if (po.init) { 13296 po.init(t, u); 13297 initializedPlugins.push(p); 13298 } 13299 } 13300 } 13301 13302 // Create all plugins 13303 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13304 13305 // Setup popup CSS path(s) 13306 if (s.popup_css !== false) { 13307 if (s.popup_css) 13308 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13309 else 13310 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13311 } 13312 13313 if (s.popup_css_add) 13314 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13315 13316 t.controlManager = new tinymce.ControlManager(t); 13317 13318 // Enables users to override the control factory 13319 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13320 13321 // Measure box 13322 if (s.render_ui && t.theme) { 13323 t.orgDisplay = e.style.display; 13324 13325 if (typeof s.theme != "function") { 13326 w = s.width || e.style.width || e.offsetWidth; 13327 h = s.height || e.style.height || e.offsetHeight; 13328 mh = s.min_height || 100; 13329 re = /^[0-9\.]+(|px)$/i; 13330 13331 if (re.test('' + w)) 13332 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13333 13334 if (re.test('' + h)) 13335 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13336 13337 // Render UI 13338 o = t.theme.renderUI({ 13339 targetNode : e, 13340 width : w, 13341 height : h, 13342 deltaWidth : s.delta_width, 13343 deltaHeight : s.delta_height 13344 }); 13345 13346 // Resize editor 13347 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13348 width : w, 13349 height : h 13350 }); 13351 13352 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13353 if (h < mh) 13354 h = mh; 13355 } else { 13356 o = s.theme(t, e); 13357 13358 // Convert element type to id:s 13359 if (o.editorContainer.nodeType) { 13360 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13361 } 13362 13363 // Convert element type to id:s 13364 if (o.iframeContainer.nodeType) { 13365 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13366 } 13367 13368 // Use specified iframe height or the targets offsetHeight 13369 h = o.iframeHeight || e.offsetHeight; 13370 13371 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13372 if (isIE) { 13373 t.onInit.add(function(ed) { 13374 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { 13375 ed.lastIERng = ed.selection.getRng(); 13376 }); 13377 }); 13378 } 13379 } 13380 13381 t.editorContainer = o.editorContainer; 13382 } 13383 13384 // Load specified content CSS last 13385 if (s.content_css) { 13386 each(explode(s.content_css), function(u) { 13387 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13388 }); 13389 } 13390 13391 // Content editable mode ends here 13392 if (s.content_editable) { 13393 e = n = o = null; // Fix IE leak 13394 return t.initContentBody(); 13395 } 13396 13397 // User specified a document.domain value 13398 if (document.domain && location.hostname != document.domain) 13399 tinymce.relaxedDomain = document.domain; 13400 13401 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13402 13403 // We only need to override paths if we have to 13404 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13405 if (s.document_base_url != tinymce.documentBaseURL) 13406 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13407 13408 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13409 if (s.ie7_compat) 13410 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13411 else 13412 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13413 13414 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13415 13416 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13417 for (i = 0; i < t.contentCSS.length; i++) { 13418 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13419 } 13420 13421 t.contentCSS = []; 13422 13423 bi = s.body_id || 'tinymce'; 13424 if (bi.indexOf('=') != -1) { 13425 bi = t.getParam('body_id', '', 'hash'); 13426 bi = bi[t.id] || bi; 13427 } 13428 13429 bc = s.body_class || ''; 13430 if (bc.indexOf('=') != -1) { 13431 bc = t.getParam('body_class', '', 'hash'); 13432 bc = bc[t.id] || ''; 13433 } 13434 13435 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13436 13437 // Domain relaxing enabled, then set document domain 13438 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13439 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13440 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13441 } 13442 13443 // Create iframe 13444 // TODO: ACC add the appropriate description on this. 13445 n = DOM.add(o.iframeContainer, 'iframe', { 13446 id : t.id + "_ifr", 13447 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13448 frameBorder : '0', 13449 allowTransparency : "true", 13450 title : s.aria_label, 13451 style : { 13452 width : '100%', 13453 height : h, 13454 display : 'block' // Important for Gecko to render the iframe correctly 13455 } 13456 }); 13457 13458 t.contentAreaContainer = o.iframeContainer; 13459 13460 if (o.editorContainer) { 13461 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13462 } 13463 13464 // Restore visibility on target element 13465 e.style.visibility = t.orgVisibility; 13466 13467 DOM.get(t.id).style.display = 'none'; 13468 DOM.setAttrib(t.id, 'aria-hidden', true); 13469 13470 if (!tinymce.relaxedDomain || !u) 13471 t.initContentBody(); 13472 13473 e = n = o = null; // Cleanup 13474 }, 13475 13476 initContentBody : function() { 13477 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13478 13479 // Setup iframe body 13480 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13481 doc.open(); 13482 doc.write(self.iframeHTML); 13483 doc.close(); 13484 13485 if (tinymce.relaxedDomain) 13486 doc.domain = tinymce.relaxedDomain; 13487 } 13488 13489 if (settings.content_editable) { 13490 DOM.addClass(targetElm, 'mceContentBody'); 13491 self.contentDocument = doc = settings.content_document || document; 13492 self.contentWindow = settings.content_window || window; 13493 self.bodyElement = targetElm; 13494 13495 // Prevent leak in IE 13496 settings.content_document = settings.content_window = null; 13497 } 13498 13499 // It will not steal focus while setting contentEditable 13500 body = self.getBody(); 13501 body.disabled = true; 13502 13503 if (!settings.readonly) 13504 body.contentEditable = self.getParam('content_editable_state', true); 13505 13506 body.disabled = false; 13507 13508 self.schema = new tinymce.html.Schema(settings); 13509 13510 self.dom = new tinymce.dom.DOMUtils(doc, { 13511 keep_values : true, 13512 url_converter : self.convertURL, 13513 url_converter_scope : self, 13514 hex_colors : settings.force_hex_style_colors, 13515 class_filter : settings.class_filter, 13516 update_styles : true, 13517 root_element : settings.content_editable ? self.id : null, 13518 schema : self.schema 13519 }); 13520 13521 self.parser = new tinymce.html.DomParser(settings, self.schema); 13522 13523 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13524 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13525 var i = nodes.length, node, dom = self.dom, value, internalName; 13526 13527 while (i--) { 13528 node = nodes[i]; 13529 value = node.attr(name); 13530 internalName = 'data-mce-' + name; 13531 13532 // Add internal attribute if we need to we don't on a refresh of the document 13533 if (!node.attributes.map[internalName]) { 13534 if (name === "style") 13535 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13536 else 13537 node.attr(internalName, self.convertURL(value, name, node.name)); 13538 } 13539 } 13540 }); 13541 13542 // Keep scripts from executing 13543 self.parser.addNodeFilter('script', function(nodes, name) { 13544 var i = nodes.length, node; 13545 13546 while (i--) { 13547 node = nodes[i]; 13548 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13549 } 13550 }); 13551 13552 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13553 var i = nodes.length, node; 13554 13555 while (i--) { 13556 node = nodes[i]; 13557 node.type = 8; 13558 node.name = '#comment'; 13559 node.value = '[CDATA[' + node.value + ']]'; 13560 } 13561 }); 13562 13563 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13564 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13565 13566 while (i--) { 13567 node = nodes[i]; 13568 13569 if (node.isEmpty(nonEmptyElements)) 13570 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13571 } 13572 }); 13573 13574 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13575 13576 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13577 13578 self.formatter = new tinymce.Formatter(self); 13579 13580 self.undoManager = new tinymce.UndoManager(self); 13581 13582 self.forceBlocks = new tinymce.ForceBlocks(self); 13583 self.enterKey = new tinymce.EnterKey(self); 13584 self.editorCommands = new tinymce.EditorCommands(self); 13585 13586 self.onExecCommand.add(function(editor, command) { 13587 // Don't refresh the select lists until caret move 13588 if (!/^(FontName|FontSize)$/.test(command)) 13589 self.nodeChanged(); 13590 }); 13591 13592 // Pass through 13593 self.serializer.onPreProcess.add(function(se, o) { 13594 return self.onPreProcess.dispatch(self, o, se); 13595 }); 13596 13597 self.serializer.onPostProcess.add(function(se, o) { 13598 return self.onPostProcess.dispatch(self, o, se); 13599 }); 13600 13601 self.onPreInit.dispatch(self); 13602 13603 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13604 doc.body.spellcheck = false; 13605 13606 if (!settings.readonly) { 13607 self.bindNativeEvents(); 13608 } 13609 13610 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13611 self.onPostRender.dispatch(self); 13612 13613 self.quirks = tinymce.util.Quirks(self); 13614 13615 if (settings.directionality) 13616 body.dir = settings.directionality; 13617 13618 if (settings.nowrap) 13619 body.style.whiteSpace = "nowrap"; 13620 13621 if (settings.protect) { 13622 self.onBeforeSetContent.add(function(ed, o) { 13623 each(settings.protect, function(pattern) { 13624 o.content = o.content.replace(pattern, function(str) { 13625 return '<!--mce:protected ' + escape(str) + '-->'; 13626 }); 13627 }); 13628 }); 13629 } 13630 13631 // Add visual aids when new contents is added 13632 self.onSetContent.add(function() { 13633 self.addVisual(self.getBody()); 13634 }); 13635 13636 // Remove empty contents 13637 if (settings.padd_empty_editor) { 13638 self.onPostProcess.add(function(ed, o) { 13639 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13640 }); 13641 } 13642 13643 self.load({initial : true, format : 'html'}); 13644 self.startContent = self.getContent({format : 'raw'}); 13645 13646 self.initialized = true; 13647 13648 self.onInit.dispatch(self); 13649 self.execCallback('setupcontent_callback', self.id, body, doc); 13650 self.execCallback('init_instance_callback', self); 13651 self.focus(true); 13652 self.nodeChanged({initial : true}); 13653 13654 // Add editor specific CSS styles 13655 if (self.contentStyles.length > 0) { 13656 contentCssText = ''; 13657 13658 each(self.contentStyles, function(style) { 13659 contentCssText += style + "\r\n"; 13660 }); 13661 13662 self.dom.addStyle(contentCssText); 13663 } 13664 13665 // Load specified content CSS last 13666 each(self.contentCSS, function(url) { 13667 self.dom.loadCSS(url); 13668 }); 13669 13670 // Handle auto focus 13671 if (settings.auto_focus) { 13672 setTimeout(function () { 13673 var ed = tinymce.get(settings.auto_focus); 13674 13675 ed.selection.select(ed.getBody(), 1); 13676 ed.selection.collapse(1); 13677 ed.getBody().focus(); 13678 ed.getWin().focus(); 13679 }, 100); 13680 } 13681 13682 // Clean up references for IE 13683 targetElm = doc = body = null; 13684 }, 13685 13686 focus : function(skip_focus) { 13687 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 13688 13689 if (!skip_focus) { 13690 if (self.lastIERng) { 13691 selection.setRng(self.lastIERng); 13692 } 13693 13694 // Get selected control element 13695 ieRng = selection.getRng(); 13696 if (ieRng.item) { 13697 controlElm = ieRng.item(0); 13698 } 13699 13700 self._refreshContentEditable(); 13701 13702 // Focus the window iframe 13703 if (!contentEditable) { 13704 self.getWin().focus(); 13705 } 13706 13707 // Focus the body as well since it's contentEditable 13708 if (tinymce.isGecko || contentEditable) { 13709 body = self.getBody(); 13710 13711 // Check for setActive since it doesn't scroll to the element 13712 if (body.setActive) { 13713 body.setActive(); 13714 } else { 13715 body.focus(); 13716 } 13717 13718 if (contentEditable) { 13719 selection.normalize(); 13720 } 13721 } 13722 13723 // Restore selected control element 13724 // This is needed when for example an image is selected within a 13725 // layer a call to focus will then remove the control selection 13726 if (controlElm && controlElm.ownerDocument == doc) { 13727 ieRng = doc.body.createControlRange(); 13728 ieRng.addElement(controlElm); 13729 ieRng.select(); 13730 } 13731 } 13732 13733 if (tinymce.activeEditor != self) { 13734 if ((oed = tinymce.activeEditor) != null) 13735 oed.onDeactivate.dispatch(oed, self); 13736 13737 self.onActivate.dispatch(self, oed); 13738 } 13739 13740 tinymce._setActive(self); 13741 }, 13742 13743 execCallback : function(n) { 13744 var t = this, f = t.settings[n], s; 13745 13746 if (!f) 13747 return; 13748 13749 // Look through lookup 13750 if (t.callbackLookup && (s = t.callbackLookup[n])) { 13751 f = s.func; 13752 s = s.scope; 13753 } 13754 13755 if (is(f, 'string')) { 13756 s = f.replace(/\.\w+$/, ''); 13757 s = s ? tinymce.resolve(s) : 0; 13758 f = tinymce.resolve(f); 13759 t.callbackLookup = t.callbackLookup || {}; 13760 t.callbackLookup[n] = {func : f, scope : s}; 13761 } 13762 13763 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 13764 }, 13765 13766 translate : function(s) { 13767 var c = this.settings.language || 'en', i18n = tinymce.i18n; 13768 13769 if (!s) 13770 return ''; 13771 13772 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 13773 return i18n[c + '.' + b] || '{#' + b + '}'; 13774 }); 13775 }, 13776 13777 getLang : function(n, dv) { 13778 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 13779 }, 13780 13781 getParam : function(n, dv, ty) { 13782 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 13783 13784 if (ty === 'hash') { 13785 o = {}; 13786 13787 if (is(v, 'string')) { 13788 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 13789 v = v.split('='); 13790 13791 if (v.length > 1) 13792 o[tr(v[0])] = tr(v[1]); 13793 else 13794 o[tr(v[0])] = tr(v); 13795 }); 13796 } else 13797 o = v; 13798 13799 return o; 13800 } 13801 13802 return v; 13803 }, 13804 13805 nodeChanged : function(o) { 13806 var self = this, selection = self.selection, node; 13807 13808 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 13809 if (self.initialized) { 13810 o = o || {}; 13811 13812 // Get start node 13813 node = selection.getStart() || self.getBody(); 13814 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 13815 13816 // Get parents and add them to object 13817 o.parents = []; 13818 self.dom.getParent(node, function(node) { 13819 if (node.nodeName == 'BODY') 13820 return true; 13821 13822 o.parents.push(node); 13823 }); 13824 13825 self.onNodeChange.dispatch( 13826 self, 13827 o ? o.controlManager || self.controlManager : self.controlManager, 13828 node, 13829 selection.isCollapsed(), 13830 o 13831 ); 13832 } 13833 }, 13834 13835 addButton : function(name, settings) { 13836 var self = this; 13837 13838 self.buttons = self.buttons || {}; 13839 self.buttons[name] = settings; 13840 }, 13841 13842 addCommand : function(name, callback, scope) { 13843 this.execCommands[name] = {func : callback, scope : scope || this}; 13844 }, 13845 13846 addQueryStateHandler : function(name, callback, scope) { 13847 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 13848 }, 13849 13850 addQueryValueHandler : function(name, callback, scope) { 13851 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 13852 }, 13853 13854 addShortcut : function(pa, desc, cmd_func, sc) { 13855 var t = this, c; 13856 13857 if (t.settings.custom_shortcuts === false) 13858 return false; 13859 13860 t.shortcuts = t.shortcuts || {}; 13861 13862 if (is(cmd_func, 'string')) { 13863 c = cmd_func; 13864 13865 cmd_func = function() { 13866 t.execCommand(c, false, null); 13867 }; 13868 } 13869 13870 if (is(cmd_func, 'object')) { 13871 c = cmd_func; 13872 13873 cmd_func = function() { 13874 t.execCommand(c[0], c[1], c[2]); 13875 }; 13876 } 13877 13878 each(explode(pa), function(pa) { 13879 var o = { 13880 func : cmd_func, 13881 scope : sc || this, 13882 desc : t.translate(desc), 13883 alt : false, 13884 ctrl : false, 13885 shift : false 13886 }; 13887 13888 each(explode(pa, '+'), function(v) { 13889 switch (v) { 13890 case 'alt': 13891 case 'ctrl': 13892 case 'shift': 13893 o[v] = true; 13894 break; 13895 13896 default: 13897 o.charCode = v.charCodeAt(0); 13898 o.keyCode = v.toUpperCase().charCodeAt(0); 13899 } 13900 }); 13901 13902 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 13903 }); 13904 13905 return true; 13906 }, 13907 13908 execCommand : function(cmd, ui, val, a) { 13909 var t = this, s = 0, o, st; 13910 13911 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 13912 t.focus(); 13913 13914 a = extend({}, a); 13915 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 13916 if (a.terminate) 13917 return false; 13918 13919 // Command callback 13920 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 13921 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13922 return true; 13923 } 13924 13925 // Registred commands 13926 if (o = t.execCommands[cmd]) { 13927 st = o.func.call(o.scope, ui, val); 13928 13929 // Fall through on true 13930 if (st !== true) { 13931 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13932 return st; 13933 } 13934 } 13935 13936 // Plugin commands 13937 each(t.plugins, function(p) { 13938 if (p.execCommand && p.execCommand(cmd, ui, val)) { 13939 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13940 s = 1; 13941 return false; 13942 } 13943 }); 13944 13945 if (s) 13946 return true; 13947 13948 // Theme commands 13949 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 13950 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13951 return true; 13952 } 13953 13954 // Editor commands 13955 if (t.editorCommands.execCommand(cmd, ui, val)) { 13956 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13957 return true; 13958 } 13959 13960 // Browser commands 13961 t.getDoc().execCommand(cmd, ui, val); 13962 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13963 }, 13964 13965 queryCommandState : function(cmd) { 13966 var t = this, o, s; 13967 13968 // Is hidden then return undefined 13969 if (t._isHidden()) 13970 return; 13971 13972 // Registred commands 13973 if (o = t.queryStateCommands[cmd]) { 13974 s = o.func.call(o.scope); 13975 13976 // Fall though on true 13977 if (s !== true) 13978 return s; 13979 } 13980 13981 // Registred commands 13982 o = t.editorCommands.queryCommandState(cmd); 13983 if (o !== -1) 13984 return o; 13985 13986 // Browser commands 13987 try { 13988 return this.getDoc().queryCommandState(cmd); 13989 } catch (ex) { 13990 // Fails sometimes see bug: 1896577 13991 } 13992 }, 13993 13994 queryCommandValue : function(c) { 13995 var t = this, o, s; 13996 13997 // Is hidden then return undefined 13998 if (t._isHidden()) 13999 return; 14000 14001 // Registred commands 14002 if (o = t.queryValueCommands[c]) { 14003 s = o.func.call(o.scope); 14004 14005 // Fall though on true 14006 if (s !== true) 14007 return s; 14008 } 14009 14010 // Registred commands 14011 o = t.editorCommands.queryCommandValue(c); 14012 if (is(o)) 14013 return o; 14014 14015 // Browser commands 14016 try { 14017 return this.getDoc().queryCommandValue(c); 14018 } catch (ex) { 14019 // Fails sometimes see bug: 1896577 14020 } 14021 }, 14022 14023 show : function() { 14024 var self = this; 14025 14026 DOM.show(self.getContainer()); 14027 DOM.hide(self.id); 14028 self.load(); 14029 }, 14030 14031 hide : function() { 14032 var self = this, doc = self.getDoc(); 14033 14034 // Fixed bug where IE has a blinking cursor left from the editor 14035 if (isIE && doc) 14036 doc.execCommand('SelectAll'); 14037 14038 // We must save before we hide so Safari doesn't crash 14039 self.save(); 14040 DOM.hide(self.getContainer()); 14041 DOM.setStyle(self.id, 'display', self.orgDisplay); 14042 }, 14043 14044 isHidden : function() { 14045 return !DOM.isHidden(this.id); 14046 }, 14047 14048 setProgressState : function(b, ti, o) { 14049 this.onSetProgressState.dispatch(this, b, ti, o); 14050 14051 return b; 14052 }, 14053 14054 load : function(o) { 14055 var t = this, e = t.getElement(), h; 14056 14057 if (e) { 14058 o = o || {}; 14059 o.load = true; 14060 14061 // Double encode existing entities in the value 14062 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14063 o.element = e; 14064 14065 if (!o.no_events) 14066 t.onLoadContent.dispatch(t, o); 14067 14068 o.element = e = null; 14069 14070 return h; 14071 } 14072 }, 14073 14074 save : function(o) { 14075 var t = this, e = t.getElement(), h, f; 14076 14077 if (!e || !t.initialized) 14078 return; 14079 14080 o = o || {}; 14081 o.save = true; 14082 14083 o.element = e; 14084 h = o.content = t.getContent(o); 14085 14086 if (!o.no_events) 14087 t.onSaveContent.dispatch(t, o); 14088 14089 h = o.content; 14090 14091 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14092 e.innerHTML = h; 14093 14094 // Update hidden form element 14095 if (f = DOM.getParent(t.id, 'form')) { 14096 each(f.elements, function(e) { 14097 if (e.name == t.id) { 14098 e.value = h; 14099 return false; 14100 } 14101 }); 14102 } 14103 } else 14104 e.value = h; 14105 14106 o.element = e = null; 14107 14108 return h; 14109 }, 14110 14111 setContent : function(content, args) { 14112 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14113 14114 // Setup args object 14115 args = args || {}; 14116 args.format = args.format || 'html'; 14117 args.set = true; 14118 args.content = content; 14119 14120 // Do preprocessing 14121 if (!args.no_events) 14122 self.onBeforeSetContent.dispatch(self, args); 14123 14124 content = args.content; 14125 14126 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14127 // It will also be impossible to place the caret in the editor unless there is a BR element present 14128 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14129 forcedRootBlockName = self.settings.forced_root_block; 14130 if (forcedRootBlockName) 14131 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14132 else 14133 content = '<br data-mce-bogus="1">'; 14134 14135 body.innerHTML = content; 14136 self.selection.select(body, true); 14137 self.selection.collapse(true); 14138 return; 14139 } 14140 14141 // Parse and serialize the html 14142 if (args.format !== 'raw') { 14143 content = new tinymce.html.Serializer({}, self.schema).serialize( 14144 self.parser.parse(content) 14145 ); 14146 } 14147 14148 // Set the new cleaned contents to the editor 14149 args.content = tinymce.trim(content); 14150 self.dom.setHTML(body, args.content); 14151 14152 // Do post processing 14153 if (!args.no_events) 14154 self.onSetContent.dispatch(self, args); 14155 14156 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14157 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14158 self.selection.normalize(); 14159 } 14160 14161 return args.content; 14162 }, 14163 14164 getContent : function(args) { 14165 var self = this, content; 14166 14167 // Setup args object 14168 args = args || {}; 14169 args.format = args.format || 'html'; 14170 args.get = true; 14171 args.getInner = true; 14172 14173 // Do preprocessing 14174 if (!args.no_events) 14175 self.onBeforeGetContent.dispatch(self, args); 14176 14177 // Get raw contents or by default the cleaned contents 14178 if (args.format == 'raw') 14179 content = self.getBody().innerHTML; 14180 else 14181 content = self.serializer.serialize(self.getBody(), args); 14182 14183 args.content = tinymce.trim(content); 14184 14185 // Do post processing 14186 if (!args.no_events) 14187 self.onGetContent.dispatch(self, args); 14188 14189 return args.content; 14190 }, 14191 14192 isDirty : function() { 14193 var self = this; 14194 14195 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14196 }, 14197 14198 getContainer : function() { 14199 var self = this; 14200 14201 if (!self.container) 14202 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14203 14204 return self.container; 14205 }, 14206 14207 getContentAreaContainer : function() { 14208 return this.contentAreaContainer; 14209 }, 14210 14211 getElement : function() { 14212 return DOM.get(this.settings.content_element || this.id); 14213 }, 14214 14215 getWin : function() { 14216 var self = this, elm; 14217 14218 if (!self.contentWindow) { 14219 elm = DOM.get(self.id + "_ifr"); 14220 14221 if (elm) 14222 self.contentWindow = elm.contentWindow; 14223 } 14224 14225 return self.contentWindow; 14226 }, 14227 14228 getDoc : function() { 14229 var self = this, win; 14230 14231 if (!self.contentDocument) { 14232 win = self.getWin(); 14233 14234 if (win) 14235 self.contentDocument = win.document; 14236 } 14237 14238 return self.contentDocument; 14239 }, 14240 14241 getBody : function() { 14242 return this.bodyElement || this.getDoc().body; 14243 }, 14244 14245 convertURL : function(url, name, elm) { 14246 var self = this, settings = self.settings; 14247 14248 // Use callback instead 14249 if (settings.urlconverter_callback) 14250 return self.execCallback('urlconverter_callback', url, elm, true, name); 14251 14252 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14253 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14254 return url; 14255 14256 // Convert to relative 14257 if (settings.relative_urls) 14258 return self.documentBaseURI.toRelative(url); 14259 14260 // Convert to absolute 14261 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14262 14263 return url; 14264 }, 14265 14266 addVisual : function(elm) { 14267 var self = this, settings = self.settings, dom = self.dom, cls; 14268 14269 elm = elm || self.getBody(); 14270 14271 if (!is(self.hasVisual)) 14272 self.hasVisual = settings.visual; 14273 14274 each(dom.select('table,a', elm), function(elm) { 14275 var value; 14276 14277 switch (elm.nodeName) { 14278 case 'TABLE': 14279 cls = settings.visual_table_class || 'mceItemTable'; 14280 value = dom.getAttrib(elm, 'border'); 14281 14282 if (!value || value == '0') { 14283 if (self.hasVisual) 14284 dom.addClass(elm, cls); 14285 else 14286 dom.removeClass(elm, cls); 14287 } 14288 14289 return; 14290 14291 case 'A': 14292 if (!dom.getAttrib(elm, 'href', false)) { 14293 value = dom.getAttrib(elm, 'name') || elm.id; 14294 cls = 'mceItemAnchor'; 14295 14296 if (value) { 14297 if (self.hasVisual) 14298 dom.addClass(elm, cls); 14299 else 14300 dom.removeClass(elm, cls); 14301 } 14302 } 14303 14304 return; 14305 } 14306 }); 14307 14308 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14309 }, 14310 14311 remove : function() { 14312 var self = this, elm = self.getContainer(); 14313 14314 if (!self.removed) { 14315 self.removed = 1; // Cancels post remove event execution 14316 self.hide(); 14317 14318 // Don't clear the window or document if content editable 14319 // is enabled since other instances might still be present 14320 if (!self.settings.content_editable) { 14321 Event.unbind(self.getWin()); 14322 Event.unbind(self.getDoc()); 14323 } 14324 14325 Event.unbind(self.getBody()); 14326 Event.clear(elm); 14327 14328 self.execCallback('remove_instance_callback', self); 14329 self.onRemove.dispatch(self); 14330 14331 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14332 self.onExecCommand.listeners = []; 14333 14334 tinymce.remove(self); 14335 DOM.remove(elm); 14336 } 14337 }, 14338 14339 destroy : function(s) { 14340 var t = this; 14341 14342 // One time is enough 14343 if (t.destroyed) 14344 return; 14345 14346 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14347 if (isGecko) { 14348 Event.unbind(t.getDoc()); 14349 Event.unbind(t.getWin()); 14350 Event.unbind(t.getBody()); 14351 } 14352 14353 if (!s) { 14354 tinymce.removeUnload(t.destroy); 14355 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14356 14357 // Manual destroy 14358 if (t.theme && t.theme.destroy) 14359 t.theme.destroy(); 14360 14361 // Destroy controls, selection and dom 14362 t.controlManager.destroy(); 14363 t.selection.destroy(); 14364 t.dom.destroy(); 14365 } 14366 14367 if (t.formElement) { 14368 t.formElement.submit = t.formElement._mceOldSubmit; 14369 t.formElement._mceOldSubmit = null; 14370 } 14371 14372 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14373 14374 if (t.selection) 14375 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14376 14377 t.destroyed = 1; 14378 }, 14379 14380 // Internal functions 14381 14382 _refreshContentEditable : function() { 14383 var self = this, body, parent; 14384 14385 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14386 if (self._isHidden()) { 14387 body = self.getBody(); 14388 parent = body.parentNode; 14389 14390 parent.removeChild(body); 14391 parent.appendChild(body); 14392 14393 body.focus(); 14394 } 14395 }, 14396 14397 _isHidden : function() { 14398 var s; 14399 14400 if (!isGecko) 14401 return 0; 14402 14403 // Weird, wheres that cursor selection? 14404 s = this.selection.getSel(); 14405 return (!s || !s.rangeCount || s.rangeCount === 0); 14406 } 14407 }); 14408 })(tinymce); 14409 (function(tinymce) { 14410 var each = tinymce.each; 14411 14412 tinymce.Editor.prototype.setupEvents = function() { 14413 var self = this, settings = self.settings; 14414 14415 // Add events to the editor 14416 each([ 14417 'onPreInit', 14418 14419 'onBeforeRenderUI', 14420 14421 'onPostRender', 14422 14423 'onLoad', 14424 14425 'onInit', 14426 14427 'onRemove', 14428 14429 'onActivate', 14430 14431 'onDeactivate', 14432 14433 'onClick', 14434 14435 'onEvent', 14436 14437 'onMouseUp', 14438 14439 'onMouseDown', 14440 14441 'onDblClick', 14442 14443 'onKeyDown', 14444 14445 'onKeyUp', 14446 14447 'onKeyPress', 14448 14449 'onContextMenu', 14450 14451 'onSubmit', 14452 14453 'onReset', 14454 14455 'onPaste', 14456 14457 'onPreProcess', 14458 14459 'onPostProcess', 14460 14461 'onBeforeSetContent', 14462 14463 'onBeforeGetContent', 14464 14465 'onSetContent', 14466 14467 'onGetContent', 14468 14469 'onLoadContent', 14470 14471 'onSaveContent', 14472 14473 'onNodeChange', 14474 14475 'onChange', 14476 14477 'onBeforeExecCommand', 14478 14479 'onExecCommand', 14480 14481 'onUndo', 14482 14483 'onRedo', 14484 14485 'onVisualAid', 14486 14487 'onSetProgressState', 14488 14489 'onSetAttrib' 14490 ], function(name) { 14491 self[name] = new tinymce.util.Dispatcher(self); 14492 }); 14493 14494 // Handle legacy cleanup_callback option 14495 if (settings.cleanup_callback) { 14496 self.onBeforeSetContent.add(function(ed, o) { 14497 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14498 }); 14499 14500 self.onPreProcess.add(function(ed, o) { 14501 if (o.set) 14502 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14503 14504 if (o.get) 14505 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14506 }); 14507 14508 self.onPostProcess.add(function(ed, o) { 14509 if (o.set) 14510 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14511 14512 if (o.get) 14513 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14514 }); 14515 } 14516 14517 // Handle legacy save_callback option 14518 if (settings.save_callback) { 14519 self.onGetContent.add(function(ed, o) { 14520 if (o.save) 14521 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14522 }); 14523 } 14524 14525 // Handle legacy handle_event_callback option 14526 if (settings.handle_event_callback) { 14527 self.onEvent.add(function(ed, e, o) { 14528 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14529 e.preventDefault(); 14530 e.stopPropagation(); 14531 } 14532 }); 14533 } 14534 14535 // Handle legacy handle_node_change_callback option 14536 if (settings.handle_node_change_callback) { 14537 self.onNodeChange.add(function(ed, cm, n) { 14538 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14539 }); 14540 } 14541 14542 // Handle legacy save_callback option 14543 if (settings.save_callback) { 14544 self.onSaveContent.add(function(ed, o) { 14545 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14546 14547 if (h) 14548 o.content = h; 14549 }); 14550 } 14551 14552 // Handle legacy onchange_callback option 14553 if (settings.onchange_callback) { 14554 self.onChange.add(function(ed, l) { 14555 ed.execCallback('onchange_callback', ed, l); 14556 }); 14557 } 14558 }; 14559 14560 tinymce.Editor.prototype.bindNativeEvents = function() { 14561 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14562 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14563 14564 nativeToDispatcherMap = { 14565 mouseup : 'onMouseUp', 14566 mousedown : 'onMouseDown', 14567 click : 'onClick', 14568 keyup : 'onKeyUp', 14569 keydown : 'onKeyDown', 14570 keypress : 'onKeyPress', 14571 submit : 'onSubmit', 14572 reset : 'onReset', 14573 contextmenu : 'onContextMenu', 14574 dblclick : 'onDblClick', 14575 paste : 'onPaste' // Doesn't work in all browsers yet 14576 }; 14577 14578 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14579 function eventHandler(evt, args) { 14580 var type = evt.type; 14581 14582 // Don't fire events when it's removed 14583 if (self.removed) 14584 return; 14585 14586 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14587 if (self.onEvent.dispatch(self, evt, args) !== false) { 14588 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14589 } 14590 }; 14591 14592 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14593 function doOperaFocus(e) { 14594 self.focus(true); 14595 }; 14596 14597 function nodeChanged(ed, e) { 14598 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14599 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14600 self.selection.normalize(); 14601 } 14602 14603 self.nodeChanged(); 14604 } 14605 14606 // Add DOM events 14607 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14608 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14609 14610 switch (nativeName) { 14611 case 'contextmenu': 14612 dom.bind(root, nativeName, eventHandler); 14613 break; 14614 14615 case 'paste': 14616 dom.bind(self.getBody(), nativeName, eventHandler); 14617 break; 14618 14619 case 'submit': 14620 case 'reset': 14621 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14622 break; 14623 14624 default: 14625 dom.bind(root, nativeName, eventHandler); 14626 } 14627 }); 14628 14629 // Set the editor as active when focused 14630 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14631 self.focus(true); 14632 }); 14633 14634 if (settings.content_editable && tinymce.isOpera) { 14635 dom.bind(self.getBody(), 'click', doOperaFocus); 14636 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14637 } 14638 14639 // Add node change handler 14640 self.onMouseUp.add(nodeChanged); 14641 14642 self.onKeyUp.add(function(ed, e) { 14643 var keyCode = e.keyCode; 14644 14645 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 14646 nodeChanged(ed, e); 14647 }); 14648 14649 // Add reset handler 14650 self.onReset.add(function() { 14651 self.setContent(self.startContent, {format : 'raw'}); 14652 }); 14653 14654 // Add shortcuts 14655 function handleShortcut(e, execute) { 14656 if (e.altKey || e.ctrlKey || e.metaKey) { 14657 each(self.shortcuts, function(shortcut) { 14658 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 14659 14660 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 14661 return; 14662 14663 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 14664 e.preventDefault(); 14665 14666 if (execute) { 14667 shortcut.func.call(shortcut.scope); 14668 } 14669 14670 return true; 14671 } 14672 }); 14673 } 14674 }; 14675 14676 self.onKeyUp.add(function(ed, e) { 14677 handleShortcut(e); 14678 }); 14679 14680 self.onKeyPress.add(function(ed, e) { 14681 handleShortcut(e); 14682 }); 14683 14684 self.onKeyDown.add(function(ed, e) { 14685 handleShortcut(e, true); 14686 }); 14687 14688 if (tinymce.isOpera) { 14689 self.onClick.add(function(ed, e) { 14690 e.preventDefault(); 14691 }); 14692 } 14693 }; 14694 })(tinymce); 14695 (function(tinymce) { 14696 // Added for compression purposes 14697 var each = tinymce.each, undef, TRUE = true, FALSE = false; 14698 14699 tinymce.EditorCommands = function(editor) { 14700 var dom = editor.dom, 14701 selection = editor.selection, 14702 commands = {state: {}, exec : {}, value : {}}, 14703 settings = editor.settings, 14704 formatter = editor.formatter, 14705 bookmark; 14706 14707 function execCommand(command, ui, value) { 14708 var func; 14709 14710 command = command.toLowerCase(); 14711 if (func = commands.exec[command]) { 14712 func(command, ui, value); 14713 return TRUE; 14714 } 14715 14716 return FALSE; 14717 }; 14718 14719 function queryCommandState(command) { 14720 var func; 14721 14722 command = command.toLowerCase(); 14723 if (func = commands.state[command]) 14724 return func(command); 14725 14726 return -1; 14727 }; 14728 14729 function queryCommandValue(command) { 14730 var func; 14731 14732 command = command.toLowerCase(); 14733 if (func = commands.value[command]) 14734 return func(command); 14735 14736 return FALSE; 14737 }; 14738 14739 function addCommands(command_list, type) { 14740 type = type || 'exec'; 14741 14742 each(command_list, function(callback, command) { 14743 each(command.toLowerCase().split(','), function(command) { 14744 commands[type][command] = callback; 14745 }); 14746 }); 14747 }; 14748 14749 // Expose public methods 14750 tinymce.extend(this, { 14751 execCommand : execCommand, 14752 queryCommandState : queryCommandState, 14753 queryCommandValue : queryCommandValue, 14754 addCommands : addCommands 14755 }); 14756 14757 // Private methods 14758 14759 function execNativeCommand(command, ui, value) { 14760 if (ui === undef) 14761 ui = FALSE; 14762 14763 if (value === undef) 14764 value = null; 14765 14766 return editor.getDoc().execCommand(command, ui, value); 14767 }; 14768 14769 function isFormatMatch(name) { 14770 return formatter.match(name); 14771 }; 14772 14773 function toggleFormat(name, value) { 14774 formatter.toggle(name, value ? {value : value} : undef); 14775 }; 14776 14777 function storeSelection(type) { 14778 bookmark = selection.getBookmark(type); 14779 }; 14780 14781 function restoreSelection() { 14782 selection.moveToBookmark(bookmark); 14783 }; 14784 14785 // Add execCommand overrides 14786 addCommands({ 14787 // Ignore these, added for compatibility 14788 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 14789 14790 // Add undo manager logic 14791 'mceEndUndoLevel,mceAddUndoLevel' : function() { 14792 editor.undoManager.add(); 14793 }, 14794 14795 'Cut,Copy,Paste' : function(command) { 14796 var doc = editor.getDoc(), failed; 14797 14798 // Try executing the native command 14799 try { 14800 execNativeCommand(command); 14801 } catch (ex) { 14802 // Command failed 14803 failed = TRUE; 14804 } 14805 14806 // Present alert message about clipboard access not being available 14807 if (failed || !doc.queryCommandSupported(command)) { 14808 if (tinymce.isGecko) { 14809 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 14810 if (state) 14811 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 14812 }); 14813 } else 14814 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 14815 } 14816 }, 14817 14818 // Override unlink command 14819 unlink : function(command) { 14820 if (selection.isCollapsed()) 14821 selection.select(selection.getNode()); 14822 14823 execNativeCommand(command); 14824 selection.collapse(FALSE); 14825 }, 14826 14827 // Override justify commands to use the text formatter engine 14828 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 14829 var align = command.substring(7); 14830 14831 // Remove all other alignments first 14832 each('left,center,right,full'.split(','), function(name) { 14833 if (align != name) 14834 formatter.remove('align' + name); 14835 }); 14836 14837 toggleFormat('align' + align); 14838 execCommand('mceRepaint'); 14839 }, 14840 14841 // Override list commands to fix WebKit bug 14842 'InsertUnorderedList,InsertOrderedList' : function(command) { 14843 var listElm, listParent; 14844 14845 execNativeCommand(command); 14846 14847 // WebKit produces lists within block elements so we need to split them 14848 // we will replace the native list creation logic to custom logic later on 14849 // TODO: Remove this when the list creation logic is removed 14850 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 14851 if (listElm) { 14852 listParent = listElm.parentNode; 14853 14854 // If list is within a text block then split that block 14855 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 14856 storeSelection(); 14857 dom.split(listParent, listElm); 14858 restoreSelection(); 14859 } 14860 } 14861 }, 14862 14863 // Override commands to use the text formatter engine 14864 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 14865 toggleFormat(command); 14866 }, 14867 14868 // Override commands to use the text formatter engine 14869 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 14870 toggleFormat(command, value); 14871 }, 14872 14873 FontSize : function(command, ui, value) { 14874 var fontClasses, fontSizes; 14875 14876 // Convert font size 1-7 to styles 14877 if (value >= 1 && value <= 7) { 14878 fontSizes = tinymce.explode(settings.font_size_style_values); 14879 fontClasses = tinymce.explode(settings.font_size_classes); 14880 14881 if (fontClasses) 14882 value = fontClasses[value - 1] || value; 14883 else 14884 value = fontSizes[value - 1] || value; 14885 } 14886 14887 toggleFormat(command, value); 14888 }, 14889 14890 RemoveFormat : function(command) { 14891 formatter.remove(command); 14892 }, 14893 14894 mceBlockQuote : function(command) { 14895 toggleFormat('blockquote'); 14896 }, 14897 14898 FormatBlock : function(command, ui, value) { 14899 return toggleFormat(value || 'p'); 14900 }, 14901 14902 mceCleanup : function() { 14903 var bookmark = selection.getBookmark(); 14904 14905 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 14906 14907 selection.moveToBookmark(bookmark); 14908 }, 14909 14910 mceRemoveNode : function(command, ui, value) { 14911 var node = value || selection.getNode(); 14912 14913 // Make sure that the body node isn't removed 14914 if (node != editor.getBody()) { 14915 storeSelection(); 14916 editor.dom.remove(node, TRUE); 14917 restoreSelection(); 14918 } 14919 }, 14920 14921 mceSelectNodeDepth : function(command, ui, value) { 14922 var counter = 0; 14923 14924 dom.getParent(selection.getNode(), function(node) { 14925 if (node.nodeType == 1 && counter++ == value) { 14926 selection.select(node); 14927 return FALSE; 14928 } 14929 }, editor.getBody()); 14930 }, 14931 14932 mceSelectNode : function(command, ui, value) { 14933 selection.select(value); 14934 }, 14935 14936 mceInsertContent : function(command, ui, value) { 14937 var parser, serializer, parentNode, rootNode, fragment, args, 14938 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 14939 14940 //selection.normalize(); 14941 14942 // Setup parser and serializer 14943 parser = editor.parser; 14944 serializer = new tinymce.html.Serializer({}, editor.schema); 14945 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 14946 14947 // Run beforeSetContent handlers on the HTML to be inserted 14948 args = {content: value, format: 'html'}; 14949 selection.onBeforeSetContent.dispatch(selection, args); 14950 value = args.content; 14951 14952 // Add caret at end of contents if it's missing 14953 if (value.indexOf('{$caret}') == -1) 14954 value += '{$caret}'; 14955 14956 // Replace the caret marker with a span bookmark element 14957 value = value.replace(/\{\$caret\}/, bookmarkHtml); 14958 14959 // Insert node maker where we will insert the new HTML and get it's parent 14960 if (!selection.isCollapsed()) 14961 editor.getDoc().execCommand('Delete', false, null); 14962 14963 parentNode = selection.getNode(); 14964 14965 // Parse the fragment within the context of the parent node 14966 args = {context : parentNode.nodeName.toLowerCase()}; 14967 fragment = parser.parse(value, args); 14968 14969 // Move the caret to a more suitable location 14970 node = fragment.lastChild; 14971 if (node.attr('id') == 'mce_marker') { 14972 marker = node; 14973 14974 for (node = node.prev; node; node = node.walk(true)) { 14975 if (node.type == 3 || !dom.isBlock(node.name)) { 14976 node.parent.insert(marker, node, node.name === 'br'); 14977 break; 14978 } 14979 } 14980 } 14981 14982 // If parser says valid we can insert the contents into that parent 14983 if (!args.invalid) { 14984 value = serializer.serialize(fragment); 14985 14986 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 14987 node = parentNode.firstChild; 14988 node2 = parentNode.lastChild; 14989 if (!node || (node === node2 && node.nodeName === 'BR')) 14990 dom.setHTML(parentNode, value); 14991 else 14992 selection.setContent(value); 14993 } else { 14994 // If the fragment was invalid within that context then we need 14995 // to parse and process the parent it's inserted into 14996 14997 // Insert bookmark node and get the parent 14998 selection.setContent(bookmarkHtml); 14999 parentNode = editor.selection.getNode(); 15000 rootNode = editor.getBody(); 15001 15002 // Opera will return the document node when selection is in root 15003 if (parentNode.nodeType == 9) 15004 parentNode = node = rootNode; 15005 else 15006 node = parentNode; 15007 15008 // Find the ancestor just before the root element 15009 while (node !== rootNode) { 15010 parentNode = node; 15011 node = node.parentNode; 15012 } 15013 15014 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15015 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15016 value = serializer.serialize( 15017 parser.parse( 15018 // Need to replace by using a function since $ in the contents would otherwise be a problem 15019 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15020 return serializer.serialize(fragment); 15021 }) 15022 ) 15023 ); 15024 15025 // Set the inner/outer HTML depending on if we are in the root or not 15026 if (parentNode == rootNode) 15027 dom.setHTML(rootNode, value); 15028 else 15029 dom.setOuterHTML(parentNode, value); 15030 } 15031 15032 marker = dom.get('mce_marker'); 15033 15034 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15035 nodeRect = dom.getRect(marker); 15036 viewPortRect = dom.getViewPort(editor.getWin()); 15037 15038 // Check if node is out side the viewport if it is then scroll to it 15039 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15040 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15041 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15042 viewportBodyElement.scrollLeft = nodeRect.x; 15043 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15044 } 15045 15046 // Move selection before marker and remove it 15047 rng = dom.createRng(); 15048 15049 // If previous sibling is a text node set the selection to the end of that node 15050 node = marker.previousSibling; 15051 if (node && node.nodeType == 3) { 15052 rng.setStart(node, node.nodeValue.length); 15053 } else { 15054 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15055 rng.setStartBefore(marker); 15056 rng.setEndBefore(marker); 15057 } 15058 15059 // Remove the marker node and set the new range 15060 dom.remove(marker); 15061 selection.setRng(rng); 15062 15063 // Dispatch after event and add any visual elements needed 15064 selection.onSetContent.dispatch(selection, args); 15065 editor.addVisual(); 15066 }, 15067 15068 mceInsertRawHTML : function(command, ui, value) { 15069 selection.setContent('tiny_mce_marker'); 15070 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15071 }, 15072 15073 mceToggleFormat : function(command, ui, value) { 15074 toggleFormat(value); 15075 }, 15076 15077 mceSetContent : function(command, ui, value) { 15078 editor.setContent(value); 15079 }, 15080 15081 'Indent,Outdent' : function(command) { 15082 var intentValue, indentUnit, value; 15083 15084 // Setup indent level 15085 intentValue = settings.indentation; 15086 indentUnit = /[a-z%]+$/i.exec(intentValue); 15087 intentValue = parseInt(intentValue); 15088 15089 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15090 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15091 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15092 formatter.apply('div'); 15093 } 15094 15095 each(selection.getSelectedBlocks(), function(element) { 15096 if (command == 'outdent') { 15097 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15098 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15099 } else 15100 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15101 }); 15102 } else 15103 execNativeCommand(command); 15104 }, 15105 15106 mceRepaint : function() { 15107 var bookmark; 15108 15109 if (tinymce.isGecko) { 15110 try { 15111 storeSelection(TRUE); 15112 15113 if (selection.getSel()) 15114 selection.getSel().selectAllChildren(editor.getBody()); 15115 15116 selection.collapse(TRUE); 15117 restoreSelection(); 15118 } catch (ex) { 15119 // Ignore 15120 } 15121 } 15122 }, 15123 15124 mceToggleFormat : function(command, ui, value) { 15125 formatter.toggle(value); 15126 }, 15127 15128 InsertHorizontalRule : function() { 15129 editor.execCommand('mceInsertContent', false, '<hr />'); 15130 }, 15131 15132 mceToggleVisualAid : function() { 15133 editor.hasVisual = !editor.hasVisual; 15134 editor.addVisual(); 15135 }, 15136 15137 mceReplaceContent : function(command, ui, value) { 15138 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15139 }, 15140 15141 mceInsertLink : function(command, ui, value) { 15142 var anchor; 15143 15144 if (typeof(value) == 'string') 15145 value = {href : value}; 15146 15147 anchor = dom.getParent(selection.getNode(), 'a'); 15148 15149 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15150 value.href = value.href.replace(' ', '%20'); 15151 15152 // Remove existing links if there could be child links or that the href isn't specified 15153 if (!anchor || !value.href) { 15154 formatter.remove('link'); 15155 } 15156 15157 // Apply new link to selection 15158 if (value.href) { 15159 formatter.apply('link', value, anchor); 15160 } 15161 }, 15162 15163 selectAll : function() { 15164 var root = dom.getRoot(), rng = dom.createRng(); 15165 15166 rng.setStart(root, 0); 15167 rng.setEnd(root, root.childNodes.length); 15168 15169 editor.selection.setRng(rng); 15170 } 15171 }); 15172 15173 // Add queryCommandState overrides 15174 addCommands({ 15175 // Override justify commands 15176 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15177 var name = 'align' + command.substring(7); 15178 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15179 var matches = tinymce.map(nodes, function(node) { 15180 return !!formatter.matchNode(node, name); 15181 }); 15182 return tinymce.inArray(matches, TRUE) !== -1; 15183 }, 15184 15185 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15186 return isFormatMatch(command); 15187 }, 15188 15189 mceBlockQuote : function() { 15190 return isFormatMatch('blockquote'); 15191 }, 15192 15193 Outdent : function() { 15194 var node; 15195 15196 if (settings.inline_styles) { 15197 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15198 return TRUE; 15199 15200 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15201 return TRUE; 15202 } 15203 15204 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15205 }, 15206 15207 'InsertUnorderedList,InsertOrderedList' : function(command) { 15208 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); 15209 } 15210 }, 'state'); 15211 15212 // Add queryCommandValue overrides 15213 addCommands({ 15214 'FontSize,FontName' : function(command) { 15215 var value = 0, parent; 15216 15217 if (parent = dom.getParent(selection.getNode(), 'span')) { 15218 if (command == 'fontsize') 15219 value = parent.style.fontSize; 15220 else 15221 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15222 } 15223 15224 return value; 15225 } 15226 }, 'value'); 15227 15228 // Add undo manager logic 15229 addCommands({ 15230 Undo : function() { 15231 editor.undoManager.undo(); 15232 }, 15233 15234 Redo : function() { 15235 editor.undoManager.redo(); 15236 } 15237 }); 15238 }; 15239 })(tinymce); 15240 15241 (function(tinymce) { 15242 var Dispatcher = tinymce.util.Dispatcher; 15243 15244 tinymce.UndoManager = function(editor) { 15245 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15246 15247 function getContent() { 15248 // Remove whitespace before/after and remove pure bogus nodes 15249 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15250 }; 15251 15252 function addNonTypingUndoLevel() { 15253 self.typing = false; 15254 self.add(); 15255 }; 15256 15257 // Create event instances 15258 onBeforeAdd = new Dispatcher(self); 15259 onAdd = new Dispatcher(self); 15260 onUndo = new Dispatcher(self); 15261 onRedo = new Dispatcher(self); 15262 15263 // Pass though onAdd event from UndoManager to Editor as onChange 15264 onAdd.add(function(undoman, level) { 15265 if (undoman.hasUndo()) 15266 return editor.onChange.dispatch(editor, level, undoman); 15267 }); 15268 15269 // Pass though onUndo event from UndoManager to Editor 15270 onUndo.add(function(undoman, level) { 15271 return editor.onUndo.dispatch(editor, level, undoman); 15272 }); 15273 15274 // Pass though onRedo event from UndoManager to Editor 15275 onRedo.add(function(undoman, level) { 15276 return editor.onRedo.dispatch(editor, level, undoman); 15277 }); 15278 15279 // Add initial undo level when the editor is initialized 15280 editor.onInit.add(function() { 15281 self.add(); 15282 }); 15283 15284 // Get position before an execCommand is processed 15285 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15286 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15287 self.beforeChange(); 15288 } 15289 }); 15290 15291 // Add undo level after an execCommand call was made 15292 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15293 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15294 self.add(); 15295 } 15296 }); 15297 15298 // Add undo level on save contents, drag end and blur/focusout 15299 editor.onSaveContent.add(addNonTypingUndoLevel); 15300 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15301 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { 15302 if (!editor.removed && self.typing) { 15303 addNonTypingUndoLevel(); 15304 } 15305 }); 15306 15307 editor.onKeyUp.add(function(editor, e) { 15308 var keyCode = e.keyCode; 15309 15310 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15311 addNonTypingUndoLevel(); 15312 } 15313 }); 15314 15315 editor.onKeyDown.add(function(editor, e) { 15316 var keyCode = e.keyCode; 15317 15318 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15319 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15320 if (self.typing) { 15321 addNonTypingUndoLevel(); 15322 } 15323 15324 return; 15325 } 15326 15327 // If key isn't shift,ctrl,alt,capslock,metakey 15328 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15329 self.beforeChange(); 15330 self.typing = true; 15331 self.add(); 15332 } 15333 }); 15334 15335 editor.onMouseDown.add(function(editor, e) { 15336 if (self.typing) { 15337 addNonTypingUndoLevel(); 15338 } 15339 }); 15340 15341 // Add keyboard shortcuts for undo/redo keys 15342 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15343 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15344 15345 self = { 15346 // Explose for debugging reasons 15347 data : data, 15348 15349 typing : false, 15350 15351 onBeforeAdd: onBeforeAdd, 15352 15353 onAdd : onAdd, 15354 15355 onUndo : onUndo, 15356 15357 onRedo : onRedo, 15358 15359 beforeChange : function() { 15360 beforeBookmark = editor.selection.getBookmark(2, true); 15361 }, 15362 15363 add : function(level) { 15364 var i, settings = editor.settings, lastLevel; 15365 15366 level = level || {}; 15367 level.content = getContent(); 15368 15369 self.onBeforeAdd.dispatch(self, level); 15370 15371 // Add undo level if needed 15372 lastLevel = data[index]; 15373 if (lastLevel && lastLevel.content == level.content) 15374 return null; 15375 15376 // Set before bookmark on previous level 15377 if (data[index]) 15378 data[index].beforeBookmark = beforeBookmark; 15379 15380 // Time to compress 15381 if (settings.custom_undo_redo_levels) { 15382 if (data.length > settings.custom_undo_redo_levels) { 15383 for (i = 0; i < data.length - 1; i++) 15384 data[i] = data[i + 1]; 15385 15386 data.length--; 15387 index = data.length; 15388 } 15389 } 15390 15391 // Get a non intrusive normalized bookmark 15392 level.bookmark = editor.selection.getBookmark(2, true); 15393 15394 // Crop array if needed 15395 if (index < data.length - 1) 15396 data.length = index + 1; 15397 15398 data.push(level); 15399 index = data.length - 1; 15400 15401 self.onAdd.dispatch(self, level); 15402 editor.isNotDirty = 0; 15403 15404 return level; 15405 }, 15406 15407 undo : function() { 15408 var level, i; 15409 15410 if (self.typing) { 15411 self.add(); 15412 self.typing = false; 15413 } 15414 15415 if (index > 0) { 15416 level = data[--index]; 15417 15418 editor.setContent(level.content, {format : 'raw'}); 15419 editor.selection.moveToBookmark(level.beforeBookmark); 15420 15421 self.onUndo.dispatch(self, level); 15422 } 15423 15424 return level; 15425 }, 15426 15427 redo : function() { 15428 var level; 15429 15430 if (index < data.length - 1) { 15431 level = data[++index]; 15432 15433 editor.setContent(level.content, {format : 'raw'}); 15434 editor.selection.moveToBookmark(level.bookmark); 15435 15436 self.onRedo.dispatch(self, level); 15437 } 15438 15439 return level; 15440 }, 15441 15442 clear : function() { 15443 data = []; 15444 index = 0; 15445 self.typing = false; 15446 }, 15447 15448 hasUndo : function() { 15449 return index > 0 || this.typing; 15450 }, 15451 15452 hasRedo : function() { 15453 return index < data.length - 1 && !this.typing; 15454 } 15455 }; 15456 15457 return self; 15458 }; 15459 })(tinymce); 15460 15461 tinymce.ForceBlocks = function(editor) { 15462 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15463 15464 function addRootBlocks() { 15465 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15466 15467 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15468 return; 15469 15470 // Check if node is wrapped in block 15471 while (node && node != rootNode) { 15472 if (blockElements[node.nodeName]) 15473 return; 15474 15475 node = node.parentNode; 15476 } 15477 15478 // Get current selection 15479 rng = selection.getRng(); 15480 if (rng.setStart) { 15481 startContainer = rng.startContainer; 15482 startOffset = rng.startOffset; 15483 endContainer = rng.endContainer; 15484 endOffset = rng.endOffset; 15485 } else { 15486 // Force control range into text range 15487 if (rng.item) { 15488 node = rng.item(0); 15489 rng = editor.getDoc().body.createTextRange(); 15490 rng.moveToElementText(node); 15491 } 15492 15493 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15494 tmpRng = rng.duplicate(); 15495 tmpRng.collapse(true); 15496 startOffset = tmpRng.move('character', offset) * -1; 15497 15498 if (!tmpRng.collapsed) { 15499 tmpRng = rng.duplicate(); 15500 tmpRng.collapse(false); 15501 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15502 } 15503 } 15504 15505 // Wrap non block elements and text nodes 15506 node = rootNode.firstChild; 15507 while (node) { 15508 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15509 if (!rootBlockNode) { 15510 rootBlockNode = dom.create(settings.forced_root_block); 15511 node.parentNode.insertBefore(rootBlockNode, node); 15512 wrapped = true; 15513 } 15514 15515 tempNode = node; 15516 node = node.nextSibling; 15517 rootBlockNode.appendChild(tempNode); 15518 } else { 15519 rootBlockNode = null; 15520 node = node.nextSibling; 15521 } 15522 } 15523 15524 if (wrapped) { 15525 if (rng.setStart) { 15526 rng.setStart(startContainer, startOffset); 15527 rng.setEnd(endContainer, endOffset); 15528 selection.setRng(rng); 15529 } else { 15530 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15531 if (isInEditorDocument) { 15532 try { 15533 rng = editor.getDoc().body.createTextRange(); 15534 rng.moveToElementText(rootNode); 15535 rng.collapse(true); 15536 rng.moveStart('character', startOffset); 15537 15538 if (endOffset > 0) 15539 rng.moveEnd('character', endOffset); 15540 15541 rng.select(); 15542 } catch (ex) { 15543 // Ignore 15544 } 15545 } 15546 } 15547 15548 editor.nodeChanged(); 15549 } 15550 }; 15551 15552 // Force root blocks 15553 if (settings.forced_root_block) { 15554 editor.onKeyUp.add(addRootBlocks); 15555 editor.onNodeChange.add(addRootBlocks); 15556 } 15557 }; 15558 15559 (function(tinymce) { 15560 // Shorten names 15561 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15562 15563 tinymce.create('tinymce.ControlManager', { 15564 ControlManager : function(ed, s) { 15565 var t = this, i; 15566 15567 s = s || {}; 15568 t.editor = ed; 15569 t.controls = {}; 15570 t.onAdd = new tinymce.util.Dispatcher(t); 15571 t.onPostRender = new tinymce.util.Dispatcher(t); 15572 t.prefix = s.prefix || ed.id + '_'; 15573 t._cls = {}; 15574 15575 t.onPostRender.add(function() { 15576 each(t.controls, function(c) { 15577 c.postRender(); 15578 }); 15579 }); 15580 }, 15581 15582 get : function(id) { 15583 return this.controls[this.prefix + id] || this.controls[id]; 15584 }, 15585 15586 setActive : function(id, s) { 15587 var c = null; 15588 15589 if (c = this.get(id)) 15590 c.setActive(s); 15591 15592 return c; 15593 }, 15594 15595 setDisabled : function(id, s) { 15596 var c = null; 15597 15598 if (c = this.get(id)) 15599 c.setDisabled(s); 15600 15601 return c; 15602 }, 15603 15604 add : function(c) { 15605 var t = this; 15606 15607 if (c) { 15608 t.controls[c.id] = c; 15609 t.onAdd.dispatch(c, t); 15610 } 15611 15612 return c; 15613 }, 15614 15615 createControl : function(name) { 15616 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15617 15618 // Build control factory cache 15619 if (!self.controlFactories) { 15620 self.controlFactories = []; 15621 each(editor.plugins, function(plugin) { 15622 if (plugin.createControl) { 15623 self.controlFactories.push(plugin); 15624 } 15625 }); 15626 } 15627 15628 // Create controls by asking cached factories 15629 factories = self.controlFactories; 15630 for (i = 0, l = factories.length; i < l; i++) { 15631 ctrl = factories[i].createControl(name, self); 15632 15633 if (ctrl) { 15634 return self.add(ctrl); 15635 } 15636 } 15637 15638 // Create sepearator 15639 if (name === "|" || name === "separator") { 15640 return self.createSeparator(); 15641 } 15642 15643 // Create control from button collection 15644 if (editor.buttons && (ctrl = editor.buttons[name])) { 15645 return self.createButton(name, ctrl); 15646 } 15647 15648 return self.add(ctrl); 15649 }, 15650 15651 createDropMenu : function(id, s, cc) { 15652 var t = this, ed = t.editor, c, bm, v, cls; 15653 15654 s = extend({ 15655 'class' : 'mceDropDown', 15656 constrain : ed.settings.constrain_menus 15657 }, s); 15658 15659 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 15660 if (v = ed.getParam('skin_variant')) 15661 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 15662 15663 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 15664 15665 id = t.prefix + id; 15666 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 15667 c = t.controls[id] = new cls(id, s); 15668 c.onAddItem.add(function(c, o) { 15669 var s = o.settings; 15670 15671 s.title = ed.getLang(s.title, s.title); 15672 15673 if (!s.onclick) { 15674 s.onclick = function(v) { 15675 if (s.cmd) 15676 ed.execCommand(s.cmd, s.ui || false, s.value); 15677 }; 15678 } 15679 }); 15680 15681 ed.onRemove.add(function() { 15682 c.destroy(); 15683 }); 15684 15685 // Fix for bug #1897785, #1898007 15686 if (tinymce.isIE) { 15687 c.onShowMenu.add(function() { 15688 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15689 ed.focus(); 15690 15691 bm = ed.selection.getBookmark(1); 15692 }); 15693 15694 c.onHideMenu.add(function() { 15695 if (bm) { 15696 ed.selection.moveToBookmark(bm); 15697 bm = 0; 15698 } 15699 }); 15700 } 15701 15702 return t.add(c); 15703 }, 15704 15705 createListBox : function(id, s, cc) { 15706 var t = this, ed = t.editor, cmd, c, cls; 15707 15708 if (t.get(id)) 15709 return null; 15710 15711 s.title = ed.translate(s.title); 15712 s.scope = s.scope || ed; 15713 15714 if (!s.onselect) { 15715 s.onselect = function(v) { 15716 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15717 }; 15718 } 15719 15720 s = extend({ 15721 title : s.title, 15722 'class' : 'mce_' + id, 15723 scope : s.scope, 15724 control_manager : t 15725 }, s); 15726 15727 id = t.prefix + id; 15728 15729 15730 function useNativeListForAccessibility(ed) { 15731 return ed.settings.use_accessible_selects && !tinymce.isGecko 15732 } 15733 15734 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 15735 c = new tinymce.ui.NativeListBox(id, s); 15736 else { 15737 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 15738 c = new cls(id, s, ed); 15739 } 15740 15741 t.controls[id] = c; 15742 15743 // Fix focus problem in Safari 15744 if (tinymce.isWebKit) { 15745 c.onPostRender.add(function(c, n) { 15746 // Store bookmark on mousedown 15747 Event.add(n, 'mousedown', function() { 15748 ed.bookmark = ed.selection.getBookmark(1); 15749 }); 15750 15751 // Restore on focus, since it might be lost 15752 Event.add(n, 'focus', function() { 15753 ed.selection.moveToBookmark(ed.bookmark); 15754 ed.bookmark = null; 15755 }); 15756 }); 15757 } 15758 15759 if (c.hideMenu) 15760 ed.onMouseDown.add(c.hideMenu, c); 15761 15762 return t.add(c); 15763 }, 15764 15765 createButton : function(id, s, cc) { 15766 var t = this, ed = t.editor, o, c, cls; 15767 15768 if (t.get(id)) 15769 return null; 15770 15771 s.title = ed.translate(s.title); 15772 s.label = ed.translate(s.label); 15773 s.scope = s.scope || ed; 15774 15775 if (!s.onclick && !s.menu_button) { 15776 s.onclick = function() { 15777 ed.execCommand(s.cmd, s.ui || false, s.value); 15778 }; 15779 } 15780 15781 s = extend({ 15782 title : s.title, 15783 'class' : 'mce_' + id, 15784 unavailable_prefix : ed.getLang('unavailable', ''), 15785 scope : s.scope, 15786 control_manager : t 15787 }, s); 15788 15789 id = t.prefix + id; 15790 15791 if (s.menu_button) { 15792 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 15793 c = new cls(id, s, ed); 15794 ed.onMouseDown.add(c.hideMenu, c); 15795 } else { 15796 cls = t._cls.button || tinymce.ui.Button; 15797 c = new cls(id, s, ed); 15798 } 15799 15800 return t.add(c); 15801 }, 15802 15803 createMenuButton : function(id, s, cc) { 15804 s = s || {}; 15805 s.menu_button = 1; 15806 15807 return this.createButton(id, s, cc); 15808 }, 15809 15810 createSplitButton : function(id, s, cc) { 15811 var t = this, ed = t.editor, cmd, c, cls; 15812 15813 if (t.get(id)) 15814 return null; 15815 15816 s.title = ed.translate(s.title); 15817 s.scope = s.scope || ed; 15818 15819 if (!s.onclick) { 15820 s.onclick = function(v) { 15821 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15822 }; 15823 } 15824 15825 if (!s.onselect) { 15826 s.onselect = function(v) { 15827 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15828 }; 15829 } 15830 15831 s = extend({ 15832 title : s.title, 15833 'class' : 'mce_' + id, 15834 scope : s.scope, 15835 control_manager : t 15836 }, s); 15837 15838 id = t.prefix + id; 15839 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 15840 c = t.add(new cls(id, s, ed)); 15841 ed.onMouseDown.add(c.hideMenu, c); 15842 15843 return c; 15844 }, 15845 15846 createColorSplitButton : function(id, s, cc) { 15847 var t = this, ed = t.editor, cmd, c, cls, bm; 15848 15849 if (t.get(id)) 15850 return null; 15851 15852 s.title = ed.translate(s.title); 15853 s.scope = s.scope || ed; 15854 15855 if (!s.onclick) { 15856 s.onclick = function(v) { 15857 if (tinymce.isIE) 15858 bm = ed.selection.getBookmark(1); 15859 15860 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15861 }; 15862 } 15863 15864 if (!s.onselect) { 15865 s.onselect = function(v) { 15866 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15867 }; 15868 } 15869 15870 s = extend({ 15871 title : s.title, 15872 'class' : 'mce_' + id, 15873 'menu_class' : ed.getParam('skin') + 'Skin', 15874 scope : s.scope, 15875 more_colors_title : ed.getLang('more_colors') 15876 }, s); 15877 15878 id = t.prefix + id; 15879 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 15880 c = new cls(id, s, ed); 15881 ed.onMouseDown.add(c.hideMenu, c); 15882 15883 // Remove the menu element when the editor is removed 15884 ed.onRemove.add(function() { 15885 c.destroy(); 15886 }); 15887 15888 // Fix for bug #1897785, #1898007 15889 if (tinymce.isIE) { 15890 c.onShowMenu.add(function() { 15891 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15892 ed.focus(); 15893 bm = ed.selection.getBookmark(1); 15894 }); 15895 15896 c.onHideMenu.add(function() { 15897 if (bm) { 15898 ed.selection.moveToBookmark(bm); 15899 bm = 0; 15900 } 15901 }); 15902 } 15903 15904 return t.add(c); 15905 }, 15906 15907 createToolbar : function(id, s, cc) { 15908 var c, t = this, cls; 15909 15910 id = t.prefix + id; 15911 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 15912 c = new cls(id, s, t.editor); 15913 15914 if (t.get(id)) 15915 return null; 15916 15917 return t.add(c); 15918 }, 15919 15920 createToolbarGroup : function(id, s, cc) { 15921 var c, t = this, cls; 15922 id = t.prefix + id; 15923 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 15924 c = new cls(id, s, t.editor); 15925 15926 if (t.get(id)) 15927 return null; 15928 15929 return t.add(c); 15930 }, 15931 15932 createSeparator : function(cc) { 15933 var cls = cc || this._cls.separator || tinymce.ui.Separator; 15934 15935 return new cls(); 15936 }, 15937 15938 setControlType : function(n, c) { 15939 return this._cls[n.toLowerCase()] = c; 15940 }, 15941 15942 destroy : function() { 15943 each(this.controls, function(c) { 15944 c.destroy(); 15945 }); 15946 15947 this.controls = null; 15948 } 15949 }); 15950 })(tinymce); 15951 15952 (function(tinymce) { 15953 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 15954 15955 tinymce.create('tinymce.WindowManager', { 15956 WindowManager : function(ed) { 15957 var t = this; 15958 15959 t.editor = ed; 15960 t.onOpen = new Dispatcher(t); 15961 t.onClose = new Dispatcher(t); 15962 t.params = {}; 15963 t.features = {}; 15964 }, 15965 15966 open : function(s, p) { 15967 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 15968 15969 // Default some options 15970 s = s || {}; 15971 p = p || {}; 15972 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 15973 sh = isOpera ? vp.h : screen.height; 15974 s.name = s.name || 'mc_' + new Date().getTime(); 15975 s.width = parseInt(s.width || 320); 15976 s.height = parseInt(s.height || 240); 15977 s.resizable = true; 15978 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 15979 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 15980 p.inline = false; 15981 p.mce_width = s.width; 15982 p.mce_height = s.height; 15983 p.mce_auto_focus = s.auto_focus; 15984 15985 if (mo) { 15986 if (isIE) { 15987 s.center = true; 15988 s.help = false; 15989 s.dialogWidth = s.width + 'px'; 15990 s.dialogHeight = s.height + 'px'; 15991 s.scroll = s.scrollbars || false; 15992 } 15993 } 15994 15995 // Build features string 15996 each(s, function(v, k) { 15997 if (tinymce.is(v, 'boolean')) 15998 v = v ? 'yes' : 'no'; 15999 16000 if (!/^(name|url)$/.test(k)) { 16001 if (isIE && mo) 16002 f += (f ? ';' : '') + k + ':' + v; 16003 else 16004 f += (f ? ',' : '') + k + '=' + v; 16005 } 16006 }); 16007 16008 t.features = s; 16009 t.params = p; 16010 t.onOpen.dispatch(t, s, p); 16011 16012 u = s.url || s.file; 16013 u = tinymce._addVer(u); 16014 16015 try { 16016 if (isIE && mo) { 16017 w = 1; 16018 window.showModalDialog(u, window, f); 16019 } else 16020 w = window.open(u, s.name, f); 16021 } catch (ex) { 16022 // Ignore 16023 } 16024 16025 if (!w) 16026 alert(t.editor.getLang('popup_blocked')); 16027 }, 16028 16029 close : function(w) { 16030 w.close(); 16031 this.onClose.dispatch(this); 16032 }, 16033 16034 createInstance : function(cl, a, b, c, d, e) { 16035 var f = tinymce.resolve(cl); 16036 16037 return new f(a, b, c, d, e); 16038 }, 16039 16040 confirm : function(t, cb, s, w) { 16041 w = w || window; 16042 16043 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16044 }, 16045 16046 alert : function(tx, cb, s, w) { 16047 var t = this; 16048 16049 w = w || window; 16050 w.alert(t._decode(t.editor.getLang(tx, tx))); 16051 16052 if (cb) 16053 cb.call(s || t); 16054 }, 16055 16056 resizeBy : function(dw, dh, win) { 16057 win.resizeBy(dw, dh); 16058 }, 16059 16060 // Internal functions 16061 16062 _decode : function(s) { 16063 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16064 } 16065 }); 16066 }(tinymce)); 16067 (function(tinymce) { 16068 tinymce.Formatter = function(ed) { 16069 var formats = {}, 16070 each = tinymce.each, 16071 dom = ed.dom, 16072 selection = ed.selection, 16073 TreeWalker = tinymce.dom.TreeWalker, 16074 rangeUtils = new tinymce.dom.RangeUtils(dom), 16075 isValid = ed.schema.isValidChild, 16076 isBlock = dom.isBlock, 16077 forcedRootBlock = ed.settings.forced_root_block, 16078 nodeIndex = dom.nodeIndex, 16079 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF', 16080 MCE_ATTR_RE = /^(src|href|style)$/, 16081 FALSE = false, 16082 TRUE = true, 16083 formatChangeData, 16084 undef, 16085 getContentEditable = dom.getContentEditable; 16086 16087 function isArray(obj) { 16088 return obj instanceof Array; 16089 }; 16090 16091 function getParents(node, selector) { 16092 return dom.getParents(node, selector, dom.getRoot()); 16093 }; 16094 16095 function isCaretNode(node) { 16096 return node.nodeType === 1 && node.id === '_mce_caret'; 16097 }; 16098 16099 function defaultFormats() { 16100 register({ 16101 alignleft : [ 16102 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16103 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16104 ], 16105 16106 aligncenter : [ 16107 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16108 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16109 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16110 ], 16111 16112 alignright : [ 16113 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16114 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16115 ], 16116 16117 alignfull : [ 16118 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16119 ], 16120 16121 bold : [ 16122 {inline : 'strong', remove : 'all'}, 16123 {inline : 'span', styles : {fontWeight : 'bold'}}, 16124 {inline : 'b', remove : 'all'} 16125 ], 16126 16127 italic : [ 16128 {inline : 'em', remove : 'all'}, 16129 {inline : 'span', styles : {fontStyle : 'italic'}}, 16130 {inline : 'i', remove : 'all'} 16131 ], 16132 16133 underline : [ 16134 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16135 {inline : 'u', remove : 'all'} 16136 ], 16137 16138 strikethrough : [ 16139 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16140 {inline : 'strike', remove : 'all'} 16141 ], 16142 16143 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16144 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16145 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16146 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16147 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16148 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16149 subscript : {inline : 'sub'}, 16150 superscript : {inline : 'sup'}, 16151 16152 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16153 onmatch : function(node) { 16154 return true; 16155 }, 16156 16157 onformat : function(elm, fmt, vars) { 16158 each(vars, function(value, key) { 16159 dom.setAttrib(elm, key, value); 16160 }); 16161 } 16162 }, 16163 16164 removeformat : [ 16165 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16166 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16167 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16168 ] 16169 }); 16170 16171 // Register default block formats 16172 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16173 register(name, {block : name, remove : 'all'}); 16174 }); 16175 16176 // Register user defined formats 16177 register(ed.settings.formats); 16178 }; 16179 16180 function addKeyboardShortcuts() { 16181 // Add some inline shortcuts 16182 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16183 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16184 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16185 16186 // BlockFormat shortcuts keys 16187 for (var i = 1; i <= 6; i++) { 16188 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16189 } 16190 16191 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16192 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16193 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16194 }; 16195 16196 // Public functions 16197 16198 function get(name) { 16199 return name ? formats[name] : formats; 16200 }; 16201 16202 function register(name, format) { 16203 if (name) { 16204 if (typeof(name) !== 'string') { 16205 each(name, function(format, name) { 16206 register(name, format); 16207 }); 16208 } else { 16209 // Force format into array and add it to internal collection 16210 format = format.length ? format : [format]; 16211 16212 each(format, function(format) { 16213 // Set deep to false by default on selector formats this to avoid removing 16214 // alignment on images inside paragraphs when alignment is changed on paragraphs 16215 if (format.deep === undef) 16216 format.deep = !format.selector; 16217 16218 // Default to true 16219 if (format.split === undef) 16220 format.split = !format.selector || format.inline; 16221 16222 // Default to true 16223 if (format.remove === undef && format.selector && !format.inline) 16224 format.remove = 'none'; 16225 16226 // Mark format as a mixed format inline + block level 16227 if (format.selector && format.inline) { 16228 format.mixed = true; 16229 format.block_expand = true; 16230 } 16231 16232 // Split classes if needed 16233 if (typeof(format.classes) === 'string') 16234 format.classes = format.classes.split(/\s+/); 16235 }); 16236 16237 formats[name] = format; 16238 } 16239 } 16240 }; 16241 16242 var getTextDecoration = function(node) { 16243 var decoration; 16244 16245 ed.dom.getParent(node, function(n) { 16246 decoration = ed.dom.getStyle(n, 'text-decoration'); 16247 return decoration && decoration !== 'none'; 16248 }); 16249 16250 return decoration; 16251 }; 16252 16253 var processUnderlineAndColor = function(node) { 16254 var textDecoration; 16255 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16256 textDecoration = getTextDecoration(node.parentNode); 16257 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16258 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16259 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16260 ed.dom.setStyle(node, 'text-decoration', null); 16261 } 16262 } 16263 }; 16264 16265 function apply(name, vars, node) { 16266 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16267 16268 function setElementFormat(elm, fmt) { 16269 fmt = fmt || format; 16270 16271 if (elm) { 16272 if (fmt.onformat) { 16273 fmt.onformat(elm, fmt, vars, node); 16274 } 16275 16276 each(fmt.styles, function(value, name) { 16277 dom.setStyle(elm, name, replaceVars(value, vars)); 16278 }); 16279 16280 each(fmt.attributes, function(value, name) { 16281 dom.setAttrib(elm, name, replaceVars(value, vars)); 16282 }); 16283 16284 each(fmt.classes, function(value) { 16285 value = replaceVars(value, vars); 16286 16287 if (!dom.hasClass(elm, value)) 16288 dom.addClass(elm, value); 16289 }); 16290 } 16291 }; 16292 function adjustSelectionToVisibleSelection() { 16293 function findSelectionEnd(start, end) { 16294 var walker = new TreeWalker(end); 16295 for (node = walker.current(); node; node = walker.prev()) { 16296 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16297 return node; 16298 } 16299 } 16300 }; 16301 16302 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16303 // as this isn't visible to the user. 16304 var rng = ed.selection.getRng(); 16305 var start = rng.startContainer; 16306 var end = rng.endContainer; 16307 16308 if (start != end && rng.endOffset === 0) { 16309 var newEnd = findSelectionEnd(start, end); 16310 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16311 16312 rng.setEnd(newEnd, endOffset); 16313 } 16314 16315 return rng; 16316 } 16317 16318 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16319 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16320 16321 // find the index of the first child list. 16322 each(node.childNodes, function(n, index) { 16323 if (n.nodeName === "UL" || n.nodeName === "OL") { 16324 listIndex = index; 16325 list = n; 16326 return false; 16327 } 16328 }); 16329 16330 // get the index of the bookmarks 16331 each(node.childNodes, function(n, index) { 16332 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16333 if (n.id == bookmark.id + "_start") { 16334 startIndex = index; 16335 } else if (n.id == bookmark.id + "_end") { 16336 endIndex = index; 16337 } 16338 } 16339 }); 16340 16341 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16342 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16343 each(tinymce.grep(node.childNodes), process); 16344 return 0; 16345 } else { 16346 currentWrapElm = dom.clone(wrapElm, FALSE); 16347 16348 // create a list of the nodes on the same side of the list as the selection 16349 each(tinymce.grep(node.childNodes), function(n, index) { 16350 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16351 nodes.push(n); 16352 n.parentNode.removeChild(n); 16353 } 16354 }); 16355 16356 // insert the wrapping element either before or after the list. 16357 if (startIndex < listIndex) { 16358 node.insertBefore(currentWrapElm, list); 16359 } else if (startIndex > listIndex) { 16360 node.insertBefore(currentWrapElm, list.nextSibling); 16361 } 16362 16363 // add the new nodes to the list. 16364 newWrappers.push(currentWrapElm); 16365 16366 each(nodes, function(node) { 16367 currentWrapElm.appendChild(node); 16368 }); 16369 16370 return currentWrapElm; 16371 } 16372 }; 16373 16374 function applyRngStyle(rng, bookmark, node_specific) { 16375 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16376 16377 // Setup wrapper element 16378 wrapName = format.inline || format.block; 16379 wrapElm = dom.create(wrapName); 16380 setElementFormat(wrapElm); 16381 16382 rangeUtils.walk(rng, function(nodes) { 16383 var currentWrapElm; 16384 16385 function process(node) { 16386 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16387 16388 lastContentEditable = contentEditable; 16389 nodeName = node.nodeName.toLowerCase(); 16390 parentName = node.parentNode.nodeName.toLowerCase(); 16391 16392 // Node has a contentEditable value 16393 if (node.nodeType === 1 && getContentEditable(node)) { 16394 lastContentEditable = contentEditable; 16395 contentEditable = getContentEditable(node) === "true"; 16396 hasContentEditableState = true; // We don't want to wrap the container only it's children 16397 } 16398 16399 // Stop wrapping on br elements 16400 if (isEq(nodeName, 'br')) { 16401 currentWrapElm = 0; 16402 16403 // Remove any br elements when we wrap things 16404 if (format.block) 16405 dom.remove(node); 16406 16407 return; 16408 } 16409 16410 // If node is wrapper type 16411 if (format.wrapper && matchNode(node, name, vars)) { 16412 currentWrapElm = 0; 16413 return; 16414 } 16415 16416 // Can we rename the block 16417 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16418 node = dom.rename(node, wrapName); 16419 setElementFormat(node); 16420 newWrappers.push(node); 16421 currentWrapElm = 0; 16422 return; 16423 } 16424 16425 // Handle selector patterns 16426 if (format.selector) { 16427 // Look for matching formats 16428 each(formatList, function(format) { 16429 // Check collapsed state if it exists 16430 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16431 return; 16432 } 16433 16434 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16435 setElementFormat(node, format); 16436 found = true; 16437 } 16438 }); 16439 16440 // Continue processing if a selector match wasn't found and a inline element is defined 16441 if (!format.inline || found) { 16442 currentWrapElm = 0; 16443 return; 16444 } 16445 } 16446 16447 // Is it valid to wrap this item 16448 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16449 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { 16450 // Start wrapping 16451 if (!currentWrapElm) { 16452 // Wrap the node 16453 currentWrapElm = dom.clone(wrapElm, FALSE); 16454 node.parentNode.insertBefore(currentWrapElm, node); 16455 newWrappers.push(currentWrapElm); 16456 } 16457 16458 currentWrapElm.appendChild(node); 16459 } else if (nodeName == 'li' && bookmark) { 16460 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16461 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16462 } else { 16463 // Start a new wrapper for possible children 16464 currentWrapElm = 0; 16465 16466 each(tinymce.grep(node.childNodes), process); 16467 16468 if (hasContentEditableState) { 16469 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16470 } 16471 16472 // End the last wrapper 16473 currentWrapElm = 0; 16474 } 16475 }; 16476 16477 // Process siblings from range 16478 each(nodes, process); 16479 }); 16480 16481 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16482 if (format.wrap_links === false) { 16483 each(newWrappers, function(node) { 16484 function process(node) { 16485 var i, currentWrapElm, children; 16486 16487 if (node.nodeName === 'A') { 16488 currentWrapElm = dom.clone(wrapElm, FALSE); 16489 newWrappers.push(currentWrapElm); 16490 16491 children = tinymce.grep(node.childNodes); 16492 for (i = 0; i < children.length; i++) 16493 currentWrapElm.appendChild(children[i]); 16494 16495 node.appendChild(currentWrapElm); 16496 } 16497 16498 each(tinymce.grep(node.childNodes), process); 16499 }; 16500 16501 process(node); 16502 }); 16503 } 16504 16505 // Cleanup 16506 16507 each(newWrappers, function(node) { 16508 var childCount; 16509 16510 function getChildCount(node) { 16511 var count = 0; 16512 16513 each(node.childNodes, function(node) { 16514 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16515 count++; 16516 }); 16517 16518 return count; 16519 }; 16520 16521 function mergeStyles(node) { 16522 var child, clone; 16523 16524 each(node.childNodes, function(node) { 16525 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16526 child = node; 16527 return FALSE; // break loop 16528 } 16529 }); 16530 16531 // If child was found and of the same type as the current node 16532 if (child && matchName(child, format)) { 16533 clone = dom.clone(child, FALSE); 16534 setElementFormat(clone); 16535 16536 dom.replace(clone, node, TRUE); 16537 dom.remove(child, 1); 16538 } 16539 16540 return clone || node; 16541 }; 16542 16543 childCount = getChildCount(node); 16544 16545 // Remove empty nodes but only if there is multiple wrappers and they are not block 16546 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16547 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16548 dom.remove(node, 1); 16549 return; 16550 } 16551 16552 if (format.inline || format.wrapper) { 16553 // Merges the current node with it's children of similar type to reduce the number of elements 16554 if (!format.exact && childCount === 1) 16555 node = mergeStyles(node); 16556 16557 // Remove/merge children 16558 each(formatList, function(format) { 16559 // Merge all children of similar type will move styles from child to parent 16560 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16561 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16562 each(dom.select(format.inline, node), function(child) { 16563 var parent; 16564 16565 // When wrap_links is set to false we don't want 16566 // to remove the format on children within links 16567 if (format.wrap_links === false) { 16568 parent = child.parentNode; 16569 16570 do { 16571 if (parent.nodeName === 'A') 16572 return; 16573 } while (parent = parent.parentNode); 16574 } 16575 16576 removeFormat(format, vars, child, format.exact ? child : null); 16577 }); 16578 }); 16579 16580 // Remove child if direct parent is of same type 16581 if (matchNode(node.parentNode, name, vars)) { 16582 dom.remove(node, 1); 16583 node = 0; 16584 return TRUE; 16585 } 16586 16587 // Look for parent with similar style format 16588 if (format.merge_with_parents) { 16589 dom.getParent(node.parentNode, function(parent) { 16590 if (matchNode(parent, name, vars)) { 16591 dom.remove(node, 1); 16592 node = 0; 16593 return TRUE; 16594 } 16595 }); 16596 } 16597 16598 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16599 if (node && format.merge_siblings !== false) { 16600 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16601 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16602 } 16603 } 16604 }); 16605 }; 16606 16607 if (format) { 16608 if (node) { 16609 if (node.nodeType) { 16610 rng = dom.createRng(); 16611 rng.setStartBefore(node); 16612 rng.setEndAfter(node); 16613 applyRngStyle(expandRng(rng, formatList), null, true); 16614 } else { 16615 applyRngStyle(node, null, true); 16616 } 16617 } else { 16618 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16619 // Obtain selection node before selection is unselected by applyRngStyle() 16620 var curSelNode = ed.selection.getNode(); 16621 16622 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 16623 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16624 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16625 apply(formatList[0].defaultBlock); 16626 } 16627 16628 // Apply formatting to selection 16629 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16630 bookmark = selection.getBookmark(); 16631 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 16632 16633 // Colored nodes should be underlined so that the color of the underline matches the text color. 16634 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 16635 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 16636 processUnderlineAndColor(curSelNode); 16637 } 16638 16639 selection.moveToBookmark(bookmark); 16640 moveStart(selection.getRng(TRUE)); 16641 ed.nodeChanged(); 16642 } else 16643 performCaretAction('apply', name, vars); 16644 } 16645 } 16646 }; 16647 16648 function remove(name, vars, node) { 16649 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 16650 16651 // Merges the styles for each node 16652 function process(node) { 16653 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 16654 16655 // Node has a contentEditable value 16656 if (node.nodeType === 1 && getContentEditable(node)) { 16657 lastContentEditable = contentEditable; 16658 contentEditable = getContentEditable(node) === "true"; 16659 hasContentEditableState = true; // We don't want to wrap the container only it's children 16660 } 16661 16662 // Grab the children first since the nodelist might be changed 16663 children = tinymce.grep(node.childNodes); 16664 16665 // Process current node 16666 if (contentEditable && !hasContentEditableState) { 16667 for (i = 0, l = formatList.length; i < l; i++) { 16668 if (removeFormat(formatList[i], vars, node, node)) 16669 break; 16670 } 16671 } 16672 16673 // Process the children 16674 if (format.deep) { 16675 if (children.length) { 16676 for (i = 0, l = children.length; i < l; i++) 16677 process(children[i]); 16678 16679 if (hasContentEditableState) { 16680 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16681 } 16682 } 16683 } 16684 }; 16685 16686 function findFormatRoot(container) { 16687 var formatRoot; 16688 16689 // Find format root 16690 each(getParents(container.parentNode).reverse(), function(parent) { 16691 var format; 16692 16693 // Find format root element 16694 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 16695 // Is the node matching the format we are looking for 16696 format = matchNode(parent, name, vars); 16697 if (format && format.split !== false) 16698 formatRoot = parent; 16699 } 16700 }); 16701 16702 return formatRoot; 16703 }; 16704 16705 function wrapAndSplit(format_root, container, target, split) { 16706 var parent, clone, lastClone, firstClone, i, formatRootParent; 16707 16708 // Format root found then clone formats and split it 16709 if (format_root) { 16710 formatRootParent = format_root.parentNode; 16711 16712 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 16713 clone = dom.clone(parent, FALSE); 16714 16715 for (i = 0; i < formatList.length; i++) { 16716 if (removeFormat(formatList[i], vars, clone, clone)) { 16717 clone = 0; 16718 break; 16719 } 16720 } 16721 16722 // Build wrapper node 16723 if (clone) { 16724 if (lastClone) 16725 clone.appendChild(lastClone); 16726 16727 if (!firstClone) 16728 firstClone = clone; 16729 16730 lastClone = clone; 16731 } 16732 } 16733 16734 // Never split block elements if the format is mixed 16735 if (split && (!format.mixed || !isBlock(format_root))) 16736 container = dom.split(format_root, container); 16737 16738 // Wrap container in cloned formats 16739 if (lastClone) { 16740 target.parentNode.insertBefore(lastClone, target); 16741 firstClone.appendChild(target); 16742 } 16743 } 16744 16745 return container; 16746 }; 16747 16748 function splitToFormatRoot(container) { 16749 return wrapAndSplit(findFormatRoot(container), container, container, true); 16750 }; 16751 16752 function unwrap(start) { 16753 var node = dom.get(start ? '_start' : '_end'), 16754 out = node[start ? 'firstChild' : 'lastChild']; 16755 16756 // If the end is placed within the start the result will be removed 16757 // So this checks if the out node is a bookmark node if it is it 16758 // checks for another more suitable node 16759 if (isBookmarkNode(out)) 16760 out = out[start ? 'firstChild' : 'lastChild']; 16761 16762 dom.remove(node, true); 16763 16764 return out; 16765 }; 16766 16767 function removeRngStyle(rng) { 16768 var startContainer, endContainer, node; 16769 16770 rng = expandRng(rng, formatList, TRUE); 16771 16772 if (format.split) { 16773 startContainer = getContainer(rng, TRUE); 16774 endContainer = getContainer(rng); 16775 16776 if (startContainer != endContainer) { 16777 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 16778 // This will happen if you tripple click a table cell and use remove formatting 16779 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 16780 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 16781 } 16782 16783 // Wrap start/end nodes in span element since these might be cloned/moved 16784 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 16785 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 16786 16787 // Split start/end 16788 splitToFormatRoot(startContainer); 16789 splitToFormatRoot(endContainer); 16790 16791 // Unwrap start/end to get real elements again 16792 startContainer = unwrap(TRUE); 16793 endContainer = unwrap(); 16794 } else 16795 startContainer = endContainer = splitToFormatRoot(startContainer); 16796 16797 // Update range positions since they might have changed after the split operations 16798 rng.startContainer = startContainer.parentNode; 16799 rng.startOffset = nodeIndex(startContainer); 16800 rng.endContainer = endContainer.parentNode; 16801 rng.endOffset = nodeIndex(endContainer) + 1; 16802 } 16803 16804 // Remove items between start/end 16805 rangeUtils.walk(rng, function(nodes) { 16806 each(nodes, function(node) { 16807 process(node); 16808 16809 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 16810 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 16811 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 16812 } 16813 }); 16814 }); 16815 }; 16816 16817 // Handle node 16818 if (node) { 16819 if (node.nodeType) { 16820 rng = dom.createRng(); 16821 rng.setStartBefore(node); 16822 rng.setEndAfter(node); 16823 removeRngStyle(rng); 16824 } else { 16825 removeRngStyle(node); 16826 } 16827 16828 return; 16829 } 16830 16831 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16832 bookmark = selection.getBookmark(); 16833 removeRngStyle(selection.getRng(TRUE)); 16834 selection.moveToBookmark(bookmark); 16835 16836 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node 16837 if (format.inline && match(name, vars, selection.getStart())) { 16838 moveStart(selection.getRng(true)); 16839 } 16840 16841 ed.nodeChanged(); 16842 } else 16843 performCaretAction('remove', name, vars); 16844 }; 16845 16846 function toggle(name, vars, node) { 16847 var fmt = get(name); 16848 16849 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 16850 remove(name, vars, node); 16851 else 16852 apply(name, vars, node); 16853 }; 16854 16855 function matchNode(node, name, vars, similar) { 16856 var formatList = get(name), format, i, classes; 16857 16858 function matchItems(node, format, item_name) { 16859 var key, value, items = format[item_name], i; 16860 16861 // Custom match 16862 if (format.onmatch) { 16863 return format.onmatch(node, format, item_name); 16864 } 16865 16866 // Check all items 16867 if (items) { 16868 // Non indexed object 16869 if (items.length === undef) { 16870 for (key in items) { 16871 if (items.hasOwnProperty(key)) { 16872 if (item_name === 'attributes') 16873 value = dom.getAttrib(node, key); 16874 else 16875 value = getStyle(node, key); 16876 16877 if (similar && !value && !format.exact) 16878 return; 16879 16880 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 16881 return; 16882 } 16883 } 16884 } else { 16885 // Only one match needed for indexed arrays 16886 for (i = 0; i < items.length; i++) { 16887 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 16888 return format; 16889 } 16890 } 16891 } 16892 16893 return format; 16894 }; 16895 16896 if (formatList && node) { 16897 // Check each format in list 16898 for (i = 0; i < formatList.length; i++) { 16899 format = formatList[i]; 16900 16901 // Name name, attributes, styles and classes 16902 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 16903 // Match classes 16904 if (classes = format.classes) { 16905 for (i = 0; i < classes.length; i++) { 16906 if (!dom.hasClass(node, classes[i])) 16907 return; 16908 } 16909 } 16910 16911 return format; 16912 } 16913 } 16914 } 16915 }; 16916 16917 function match(name, vars, node) { 16918 var startNode; 16919 16920 function matchParents(node) { 16921 // Find first node with similar format settings 16922 node = dom.getParent(node, function(node) { 16923 return !!matchNode(node, name, vars, true); 16924 }); 16925 16926 // Do an exact check on the similar format element 16927 return matchNode(node, name, vars); 16928 }; 16929 16930 // Check specified node 16931 if (node) 16932 return matchParents(node); 16933 16934 // Check selected node 16935 node = selection.getNode(); 16936 if (matchParents(node)) 16937 return TRUE; 16938 16939 // Check start node if it's different 16940 startNode = selection.getStart(); 16941 if (startNode != node) { 16942 if (matchParents(startNode)) 16943 return TRUE; 16944 } 16945 16946 return FALSE; 16947 }; 16948 16949 function matchAll(names, vars) { 16950 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 16951 16952 // Check start of selection for formats 16953 startElement = selection.getStart(); 16954 dom.getParent(startElement, function(node) { 16955 var i, name; 16956 16957 for (i = 0; i < names.length; i++) { 16958 name = names[i]; 16959 16960 if (!checkedMap[name] && matchNode(node, name, vars)) { 16961 checkedMap[name] = true; 16962 matchedFormatNames.push(name); 16963 } 16964 } 16965 }, dom.getRoot()); 16966 16967 return matchedFormatNames; 16968 }; 16969 16970 function canApply(name) { 16971 var formatList = get(name), startNode, parents, i, x, selector; 16972 16973 if (formatList) { 16974 startNode = selection.getStart(); 16975 parents = getParents(startNode); 16976 16977 for (x = formatList.length - 1; x >= 0; x--) { 16978 selector = formatList[x].selector; 16979 16980 // Format is not selector based, then always return TRUE 16981 if (!selector) 16982 return TRUE; 16983 16984 for (i = parents.length - 1; i >= 0; i--) { 16985 if (dom.is(parents[i], selector)) 16986 return TRUE; 16987 } 16988 } 16989 } 16990 16991 return FALSE; 16992 }; 16993 16994 function formatChanged(formats, callback) { 16995 var currentFormats; 16996 16997 // Setup format node change logic 16998 if (!formatChangeData) { 16999 formatChangeData = {}; 17000 currentFormats = {}; 17001 17002 ed.onNodeChange.addToTop(function(ed, cm, node) { 17003 var parents = getParents(node), matchedFormats = {}; 17004 17005 // Check for new formats 17006 each(formatChangeData, function(callbacks, format) { 17007 each(parents, function(node) { 17008 if (matchNode(node, format, {}, true)) { 17009 if (!currentFormats[format]) { 17010 // Execute callbacks 17011 each(callbacks, function(callback) { 17012 callback(true, {node: node, format: format, parents: parents}); 17013 }); 17014 17015 currentFormats[format] = callbacks; 17016 } 17017 17018 matchedFormats[format] = callbacks; 17019 return false; 17020 } 17021 }); 17022 }); 17023 17024 // Check if current formats still match 17025 each(currentFormats, function(callbacks, format) { 17026 if (!matchedFormats[format]) { 17027 delete currentFormats[format]; 17028 17029 each(callbacks, function(callback) { 17030 callback(false, {node: node, format: format, parents: parents}); 17031 }); 17032 } 17033 }); 17034 }); 17035 } 17036 17037 // Add format listeners 17038 each(formats.split(','), function(format) { 17039 if (!formatChangeData[format]) { 17040 formatChangeData[format] = []; 17041 } 17042 17043 formatChangeData[format].push(callback); 17044 }); 17045 17046 return this; 17047 }; 17048 17049 // Expose to public 17050 tinymce.extend(this, { 17051 get : get, 17052 register : register, 17053 apply : apply, 17054 remove : remove, 17055 toggle : toggle, 17056 match : match, 17057 matchAll : matchAll, 17058 matchNode : matchNode, 17059 canApply : canApply, 17060 formatChanged: formatChanged 17061 }); 17062 17063 // Initialize 17064 defaultFormats(); 17065 addKeyboardShortcuts(); 17066 17067 // Private functions 17068 17069 function matchName(node, format) { 17070 // Check for inline match 17071 if (isEq(node, format.inline)) 17072 return TRUE; 17073 17074 // Check for block match 17075 if (isEq(node, format.block)) 17076 return TRUE; 17077 17078 // Check for selector match 17079 if (format.selector) 17080 return dom.is(node, format.selector); 17081 }; 17082 17083 function isEq(str1, str2) { 17084 str1 = str1 || ''; 17085 str2 = str2 || ''; 17086 17087 str1 = '' + (str1.nodeName || str1); 17088 str2 = '' + (str2.nodeName || str2); 17089 17090 return str1.toLowerCase() == str2.toLowerCase(); 17091 }; 17092 17093 function getStyle(node, name) { 17094 var styleVal = dom.getStyle(node, name); 17095 17096 // Force the format to hex 17097 if (name == 'color' || name == 'backgroundColor') 17098 styleVal = dom.toHex(styleVal); 17099 17100 // Opera will return bold as 700 17101 if (name == 'fontWeight' && styleVal == 700) 17102 styleVal = 'bold'; 17103 17104 return '' + styleVal; 17105 }; 17106 17107 function replaceVars(value, vars) { 17108 if (typeof(value) != "string") 17109 value = value(vars); 17110 else if (vars) { 17111 value = value.replace(/%(\w+)/g, function(str, name) { 17112 return vars[name] || str; 17113 }); 17114 } 17115 17116 return value; 17117 }; 17118 17119 function isWhiteSpaceNode(node) { 17120 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17121 }; 17122 17123 function wrap(node, name, attrs) { 17124 var wrapper = dom.create(name, attrs); 17125 17126 node.parentNode.insertBefore(wrapper, node); 17127 wrapper.appendChild(node); 17128 17129 return wrapper; 17130 }; 17131 17132 function expandRng(rng, format, remove) { 17133 var sibling, lastIdx, leaf, endPoint, 17134 startContainer = rng.startContainer, 17135 startOffset = rng.startOffset, 17136 endContainer = rng.endContainer, 17137 endOffset = rng.endOffset; 17138 17139 // This function walks up the tree if there is no siblings before/after the node 17140 function findParentContainer(start) { 17141 var container, parent, child, sibling, siblingName, root; 17142 17143 container = parent = start ? startContainer : endContainer; 17144 siblingName = start ? 'previousSibling' : 'nextSibling'; 17145 root = dom.getRoot(); 17146 17147 // If it's a text node and the offset is inside the text 17148 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17149 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17150 return container; 17151 } 17152 } 17153 17154 for (;;) { 17155 // Stop expanding on block elements 17156 if (!format[0].block_expand && isBlock(parent)) 17157 return parent; 17158 17159 // Walk left/right 17160 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17161 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { 17162 return parent; 17163 } 17164 } 17165 17166 // Check if we can move up are we at root level or body level 17167 if (parent.parentNode == root) { 17168 container = parent; 17169 break; 17170 } 17171 17172 parent = parent.parentNode; 17173 } 17174 17175 return container; 17176 }; 17177 17178 // This function walks down the tree to find the leaf at the selection. 17179 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17180 function findLeaf(node, offset) { 17181 if (offset === undef) 17182 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17183 while (node && node.hasChildNodes()) { 17184 node = node.childNodes[offset]; 17185 if (node) 17186 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17187 } 17188 return { node: node, offset: offset }; 17189 } 17190 17191 // If index based start position then resolve it 17192 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17193 lastIdx = startContainer.childNodes.length - 1; 17194 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17195 17196 if (startContainer.nodeType == 3) 17197 startOffset = 0; 17198 } 17199 17200 // If index based end position then resolve it 17201 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17202 lastIdx = endContainer.childNodes.length - 1; 17203 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17204 17205 if (endContainer.nodeType == 3) 17206 endOffset = endContainer.nodeValue.length; 17207 } 17208 17209 // Expands the node to the closes contentEditable false element if it exists 17210 function findParentContentEditable(node) { 17211 var parent = node; 17212 17213 while (parent) { 17214 if (parent.nodeType === 1 && getContentEditable(parent)) { 17215 return getContentEditable(parent) === "false" ? parent : node; 17216 } 17217 17218 parent = parent.parentNode; 17219 } 17220 17221 return node; 17222 }; 17223 17224 function findWordEndPoint(container, offset, start) { 17225 var walker, node, pos, lastTextNode; 17226 17227 function findSpace(node, offset) { 17228 var pos, pos2, str = node.nodeValue; 17229 17230 if (typeof(offset) == "undefined") { 17231 offset = start ? str.length : 0; 17232 } 17233 17234 if (start) { 17235 pos = str.lastIndexOf(' ', offset); 17236 pos2 = str.lastIndexOf('\u00a0', offset); 17237 pos = pos > pos2 ? pos : pos2; 17238 17239 // Include the space on remove to avoid tag soup 17240 if (pos !== -1 && !remove) { 17241 pos++; 17242 } 17243 } else { 17244 pos = str.indexOf(' ', offset); 17245 pos2 = str.indexOf('\u00a0', offset); 17246 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17247 } 17248 17249 return pos; 17250 }; 17251 17252 if (container.nodeType === 3) { 17253 pos = findSpace(container, offset); 17254 17255 if (pos !== -1) { 17256 return {container : container, offset : pos}; 17257 } 17258 17259 lastTextNode = container; 17260 } 17261 17262 // Walk the nodes inside the block 17263 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17264 while (node = walker[start ? 'prev' : 'next']()) { 17265 if (node.nodeType === 3) { 17266 lastTextNode = node; 17267 pos = findSpace(node); 17268 17269 if (pos !== -1) { 17270 return {container : node, offset : pos}; 17271 } 17272 } else if (isBlock(node)) { 17273 break; 17274 } 17275 } 17276 17277 if (lastTextNode) { 17278 if (start) { 17279 offset = 0; 17280 } else { 17281 offset = lastTextNode.length; 17282 } 17283 17284 return {container: lastTextNode, offset: offset}; 17285 } 17286 }; 17287 17288 function findSelectorEndPoint(container, sibling_name) { 17289 var parents, i, y, curFormat; 17290 17291 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17292 container = container[sibling_name]; 17293 17294 parents = getParents(container); 17295 for (i = 0; i < parents.length; i++) { 17296 for (y = 0; y < format.length; y++) { 17297 curFormat = format[y]; 17298 17299 // If collapsed state is set then skip formats that doesn't match that 17300 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17301 continue; 17302 17303 if (dom.is(parents[i], curFormat.selector)) 17304 return parents[i]; 17305 } 17306 } 17307 17308 return container; 17309 }; 17310 17311 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17312 var node; 17313 17314 // Expand to block of similar type 17315 if (!format[0].wrapper) 17316 node = dom.getParent(container, format[0].block); 17317 17318 // Expand to first wrappable block element or any block element 17319 if (!node) 17320 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); 17321 17322 // Exclude inner lists from wrapping 17323 if (node && format[0].wrapper) 17324 node = getParents(node, 'ul,ol').reverse()[0] || node; 17325 17326 // Didn't find a block element look for first/last wrappable element 17327 if (!node) { 17328 node = container; 17329 17330 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17331 node = node[sibling_name]; 17332 17333 // Break on BR but include it will be removed later on 17334 // we can't remove it now since we need to check if it can be wrapped 17335 if (isEq(node, 'br')) 17336 break; 17337 } 17338 } 17339 17340 return node || container; 17341 }; 17342 17343 // Expand to closest contentEditable element 17344 startContainer = findParentContentEditable(startContainer); 17345 endContainer = findParentContentEditable(endContainer); 17346 17347 // Exclude bookmark nodes if possible 17348 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17349 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17350 startContainer = startContainer.nextSibling || startContainer; 17351 17352 if (startContainer.nodeType == 3) 17353 startOffset = 0; 17354 } 17355 17356 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17357 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17358 endContainer = endContainer.previousSibling || endContainer; 17359 17360 if (endContainer.nodeType == 3) 17361 endOffset = endContainer.length; 17362 } 17363 17364 if (format[0].inline) { 17365 if (rng.collapsed) { 17366 // Expand left to closest word boundery 17367 endPoint = findWordEndPoint(startContainer, startOffset, true); 17368 if (endPoint) { 17369 startContainer = endPoint.container; 17370 startOffset = endPoint.offset; 17371 } 17372 17373 // Expand right to closest word boundery 17374 endPoint = findWordEndPoint(endContainer, endOffset); 17375 if (endPoint) { 17376 endContainer = endPoint.container; 17377 endOffset = endPoint.offset; 17378 } 17379 } 17380 17381 // Avoid applying formatting to a trailing space. 17382 leaf = findLeaf(endContainer, endOffset); 17383 if (leaf.node) { 17384 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17385 leaf = findLeaf(leaf.node.previousSibling); 17386 17387 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17388 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17389 17390 if (leaf.offset > 1) { 17391 endContainer = leaf.node; 17392 endContainer.splitText(leaf.offset - 1); 17393 } 17394 } 17395 } 17396 } 17397 17398 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17399 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17400 // This will reduce the number of wrapper elements that needs to be created 17401 // Move start point up the tree 17402 if (format[0].inline || format[0].block_expand) { 17403 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17404 startContainer = findParentContainer(true); 17405 } 17406 17407 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17408 endContainer = findParentContainer(); 17409 } 17410 } 17411 17412 // Expand start/end container to matching selector 17413 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17414 // Find new startContainer/endContainer if there is better one 17415 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17416 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17417 } 17418 17419 // Expand start/end container to matching block element or text node 17420 if (format[0].block || format[0].selector) { 17421 // Find new startContainer/endContainer if there is better one 17422 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17423 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17424 17425 // Non block element then try to expand up the leaf 17426 if (format[0].block) { 17427 if (!isBlock(startContainer)) 17428 startContainer = findParentContainer(true); 17429 17430 if (!isBlock(endContainer)) 17431 endContainer = findParentContainer(); 17432 } 17433 } 17434 17435 // Setup index for startContainer 17436 if (startContainer.nodeType == 1) { 17437 startOffset = nodeIndex(startContainer); 17438 startContainer = startContainer.parentNode; 17439 } 17440 17441 // Setup index for endContainer 17442 if (endContainer.nodeType == 1) { 17443 endOffset = nodeIndex(endContainer) + 1; 17444 endContainer = endContainer.parentNode; 17445 } 17446 17447 // Return new range like object 17448 return { 17449 startContainer : startContainer, 17450 startOffset : startOffset, 17451 endContainer : endContainer, 17452 endOffset : endOffset 17453 }; 17454 } 17455 17456 function removeFormat(format, vars, node, compare_node) { 17457 var i, attrs, stylesModified; 17458 17459 // Check if node matches format 17460 if (!matchName(node, format)) 17461 return FALSE; 17462 17463 // Should we compare with format attribs and styles 17464 if (format.remove != 'all') { 17465 // Remove styles 17466 each(format.styles, function(value, name) { 17467 value = replaceVars(value, vars); 17468 17469 // Indexed array 17470 if (typeof(name) === 'number') { 17471 name = value; 17472 compare_node = 0; 17473 } 17474 17475 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17476 dom.setStyle(node, name, ''); 17477 17478 stylesModified = 1; 17479 }); 17480 17481 // Remove style attribute if it's empty 17482 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17483 node.removeAttribute('style'); 17484 node.removeAttribute('data-mce-style'); 17485 } 17486 17487 // Remove attributes 17488 each(format.attributes, function(value, name) { 17489 var valueOut; 17490 17491 value = replaceVars(value, vars); 17492 17493 // Indexed array 17494 if (typeof(name) === 'number') { 17495 name = value; 17496 compare_node = 0; 17497 } 17498 17499 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17500 // Keep internal classes 17501 if (name == 'class') { 17502 value = dom.getAttrib(node, name); 17503 if (value) { 17504 // Build new class value where everything is removed except the internal prefixed classes 17505 valueOut = ''; 17506 each(value.split(/\s+/), function(cls) { 17507 if (/mce\w+/.test(cls)) 17508 valueOut += (valueOut ? ' ' : '') + cls; 17509 }); 17510 17511 // We got some internal classes left 17512 if (valueOut) { 17513 dom.setAttrib(node, name, valueOut); 17514 return; 17515 } 17516 } 17517 } 17518 17519 // IE6 has a bug where the attribute doesn't get removed correctly 17520 if (name == "class") 17521 node.removeAttribute('className'); 17522 17523 // Remove mce prefixed attributes 17524 if (MCE_ATTR_RE.test(name)) 17525 node.removeAttribute('data-mce-' + name); 17526 17527 node.removeAttribute(name); 17528 } 17529 }); 17530 17531 // Remove classes 17532 each(format.classes, function(value) { 17533 value = replaceVars(value, vars); 17534 17535 if (!compare_node || dom.hasClass(compare_node, value)) 17536 dom.removeClass(node, value); 17537 }); 17538 17539 // Check for non internal attributes 17540 attrs = dom.getAttribs(node); 17541 for (i = 0; i < attrs.length; i++) { 17542 if (attrs[i].nodeName.indexOf('_') !== 0) 17543 return FALSE; 17544 } 17545 } 17546 17547 // Remove the inline child if it's empty for example <b> or <span> 17548 if (format.remove != 'none') { 17549 removeNode(node, format); 17550 return TRUE; 17551 } 17552 }; 17553 17554 function removeNode(node, format) { 17555 var parentNode = node.parentNode, rootBlockElm; 17556 17557 function find(node, next, inc) { 17558 node = getNonWhiteSpaceSibling(node, next, inc); 17559 17560 return !node || (node.nodeName == 'BR' || isBlock(node)); 17561 }; 17562 17563 if (format.block) { 17564 if (!forcedRootBlock) { 17565 // Append BR elements if needed before we remove the block 17566 if (isBlock(node) && !isBlock(parentNode)) { 17567 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17568 node.insertBefore(dom.create('br'), node.firstChild); 17569 17570 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17571 node.appendChild(dom.create('br')); 17572 } 17573 } else { 17574 // Wrap the block in a forcedRootBlock if we are at the root of document 17575 if (parentNode == dom.getRoot()) { 17576 if (!format.list_block || !isEq(node, format.list_block)) { 17577 each(tinymce.grep(node.childNodes), function(node) { 17578 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17579 if (!rootBlockElm) 17580 rootBlockElm = wrap(node, forcedRootBlock); 17581 else 17582 rootBlockElm.appendChild(node); 17583 } else 17584 rootBlockElm = 0; 17585 }); 17586 } 17587 } 17588 } 17589 } 17590 17591 // Never remove nodes that isn't the specified inline element if a selector is specified too 17592 if (format.selector && format.inline && !isEq(format.inline, node)) 17593 return; 17594 17595 dom.remove(node, 1); 17596 }; 17597 17598 function getNonWhiteSpaceSibling(node, next, inc) { 17599 if (node) { 17600 next = next ? 'nextSibling' : 'previousSibling'; 17601 17602 for (node = inc ? node : node[next]; node; node = node[next]) { 17603 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17604 return node; 17605 } 17606 } 17607 }; 17608 17609 function isBookmarkNode(node) { 17610 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17611 }; 17612 17613 function mergeSiblings(prev, next) { 17614 var marker, sibling, tmpSibling; 17615 17616 function compareElements(node1, node2) { 17617 // Not the same name 17618 if (node1.nodeName != node2.nodeName) 17619 return FALSE; 17620 17621 function getAttribs(node) { 17622 var attribs = {}; 17623 17624 each(dom.getAttribs(node), function(attr) { 17625 var name = attr.nodeName.toLowerCase(); 17626 17627 // Don't compare internal attributes or style 17628 if (name.indexOf('_') !== 0 && name !== 'style') 17629 attribs[name] = dom.getAttrib(node, name); 17630 }); 17631 17632 return attribs; 17633 }; 17634 17635 function compareObjects(obj1, obj2) { 17636 var value, name; 17637 17638 for (name in obj1) { 17639 // Obj1 has item obj2 doesn't have 17640 if (obj1.hasOwnProperty(name)) { 17641 value = obj2[name]; 17642 17643 // Obj2 doesn't have obj1 item 17644 if (value === undef) 17645 return FALSE; 17646 17647 // Obj2 item has a different value 17648 if (obj1[name] != value) 17649 return FALSE; 17650 17651 // Delete similar value 17652 delete obj2[name]; 17653 } 17654 } 17655 17656 // Check if obj 2 has something obj 1 doesn't have 17657 for (name in obj2) { 17658 // Obj2 has item obj1 doesn't have 17659 if (obj2.hasOwnProperty(name)) 17660 return FALSE; 17661 } 17662 17663 return TRUE; 17664 }; 17665 17666 // Attribs are not the same 17667 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 17668 return FALSE; 17669 17670 // Styles are not the same 17671 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 17672 return FALSE; 17673 17674 return TRUE; 17675 }; 17676 17677 function findElementSibling(node, sibling_name) { 17678 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 17679 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 17680 return node; 17681 17682 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 17683 return sibling; 17684 } 17685 17686 return node; 17687 }; 17688 17689 // Check if next/prev exists and that they are elements 17690 if (prev && next) { 17691 // If previous sibling is empty then jump over it 17692 prev = findElementSibling(prev, 'previousSibling'); 17693 next = findElementSibling(next, 'nextSibling'); 17694 17695 // Compare next and previous nodes 17696 if (compareElements(prev, next)) { 17697 // Append nodes between 17698 for (sibling = prev.nextSibling; sibling && sibling != next;) { 17699 tmpSibling = sibling; 17700 sibling = sibling.nextSibling; 17701 prev.appendChild(tmpSibling); 17702 } 17703 17704 // Remove next node 17705 dom.remove(next); 17706 17707 // Move children into prev node 17708 each(tinymce.grep(next.childNodes), function(node) { 17709 prev.appendChild(node); 17710 }); 17711 17712 return prev; 17713 } 17714 } 17715 17716 return next; 17717 }; 17718 17719 function isTextBlock(name) { 17720 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); 17721 }; 17722 17723 function getContainer(rng, start) { 17724 var container, offset, lastIdx, walker; 17725 17726 container = rng[start ? 'startContainer' : 'endContainer']; 17727 offset = rng[start ? 'startOffset' : 'endOffset']; 17728 17729 if (container.nodeType == 1) { 17730 lastIdx = container.childNodes.length - 1; 17731 17732 if (!start && offset) 17733 offset--; 17734 17735 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 17736 } 17737 17738 // If start text node is excluded then walk to the next node 17739 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 17740 container = new TreeWalker(container, ed.getBody()).next() || container; 17741 } 17742 17743 // If end text node is excluded then walk to the previous node 17744 if (container.nodeType === 3 && !start && offset === 0) { 17745 container = new TreeWalker(container, ed.getBody()).prev() || container; 17746 } 17747 17748 return container; 17749 }; 17750 17751 function performCaretAction(type, name, vars) { 17752 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 17753 17754 // Creates a caret container bogus element 17755 function createCaretContainer(fill) { 17756 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 17757 17758 if (fill) { 17759 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 17760 } 17761 17762 return caretContainer; 17763 }; 17764 17765 function isCaretContainerEmpty(node, nodes) { 17766 while (node) { 17767 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 17768 return false; 17769 } 17770 17771 // Collect nodes 17772 if (nodes && node.nodeType === 1) { 17773 nodes.push(node); 17774 } 17775 17776 node = node.firstChild; 17777 } 17778 17779 return true; 17780 }; 17781 17782 // Returns any parent caret container element 17783 function getParentCaretContainer(node) { 17784 while (node) { 17785 if (node.id === caretContainerId) { 17786 return node; 17787 } 17788 17789 node = node.parentNode; 17790 } 17791 }; 17792 17793 // Finds the first text node in the specified node 17794 function findFirstTextNode(node) { 17795 var walker; 17796 17797 if (node) { 17798 walker = new TreeWalker(node, node); 17799 17800 for (node = walker.current(); node; node = walker.next()) { 17801 if (node.nodeType === 3) { 17802 return node; 17803 } 17804 } 17805 } 17806 }; 17807 17808 // Removes the caret container for the specified node or all on the current document 17809 function removeCaretContainer(node, move_caret) { 17810 var child, rng; 17811 17812 if (!node) { 17813 node = getParentCaretContainer(selection.getStart()); 17814 17815 if (!node) { 17816 while (node = dom.get(caretContainerId)) { 17817 removeCaretContainer(node, false); 17818 } 17819 } 17820 } else { 17821 rng = selection.getRng(true); 17822 17823 if (isCaretContainerEmpty(node)) { 17824 if (move_caret !== false) { 17825 rng.setStartBefore(node); 17826 rng.setEndBefore(node); 17827 } 17828 17829 dom.remove(node); 17830 } else { 17831 child = findFirstTextNode(node); 17832 17833 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 17834 child = child.deleteData(0, 1); 17835 } 17836 17837 dom.remove(node, 1); 17838 } 17839 17840 selection.setRng(rng); 17841 } 17842 }; 17843 17844 // Applies formatting to the caret postion 17845 function applyCaretFormat() { 17846 var rng, caretContainer, textNode, offset, bookmark, container, text; 17847 17848 rng = selection.getRng(true); 17849 offset = rng.startOffset; 17850 container = rng.startContainer; 17851 text = container.nodeValue; 17852 17853 caretContainer = getParentCaretContainer(selection.getStart()); 17854 if (caretContainer) { 17855 textNode = findFirstTextNode(caretContainer); 17856 } 17857 17858 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 17859 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 17860 // Get bookmark of caret position 17861 bookmark = selection.getBookmark(); 17862 17863 // Collapse bookmark range (WebKit) 17864 rng.collapse(true); 17865 17866 // Expand the range to the closest word and split it at those points 17867 rng = expandRng(rng, get(name)); 17868 rng = rangeUtils.split(rng); 17869 17870 // Apply the format to the range 17871 apply(name, vars, rng); 17872 17873 // Move selection back to caret position 17874 selection.moveToBookmark(bookmark); 17875 } else { 17876 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 17877 caretContainer = createCaretContainer(true); 17878 textNode = caretContainer.firstChild; 17879 17880 rng.insertNode(caretContainer); 17881 offset = 1; 17882 17883 apply(name, vars, caretContainer); 17884 } else { 17885 apply(name, vars, caretContainer); 17886 } 17887 17888 // Move selection to text node 17889 selection.setCursorLocation(textNode, offset); 17890 } 17891 }; 17892 17893 function removeCaretFormat() { 17894 var rng = selection.getRng(true), container, offset, bookmark, 17895 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 17896 17897 container = rng.startContainer; 17898 offset = rng.startOffset; 17899 node = container; 17900 17901 if (container.nodeType == 3) { 17902 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 17903 hasContentAfter = true; 17904 } 17905 17906 node = node.parentNode; 17907 } 17908 17909 while (node) { 17910 if (matchNode(node, name, vars)) { 17911 formatNode = node; 17912 break; 17913 } 17914 17915 if (node.nextSibling) { 17916 hasContentAfter = true; 17917 } 17918 17919 parents.push(node); 17920 node = node.parentNode; 17921 } 17922 17923 // Node doesn't have the specified format 17924 if (!formatNode) { 17925 return; 17926 } 17927 17928 // Is there contents after the caret then remove the format on the element 17929 if (hasContentAfter) { 17930 // Get bookmark of caret position 17931 bookmark = selection.getBookmark(); 17932 17933 // Collapse bookmark range (WebKit) 17934 rng.collapse(true); 17935 17936 // Expand the range to the closest word and split it at those points 17937 rng = expandRng(rng, get(name), true); 17938 rng = rangeUtils.split(rng); 17939 17940 // Remove the format from the range 17941 remove(name, vars, rng); 17942 17943 // Move selection back to caret position 17944 selection.moveToBookmark(bookmark); 17945 } else { 17946 caretContainer = createCaretContainer(); 17947 17948 node = caretContainer; 17949 for (i = parents.length - 1; i >= 0; i--) { 17950 node.appendChild(dom.clone(parents[i], false)); 17951 node = node.firstChild; 17952 } 17953 17954 // Insert invisible character into inner most format element 17955 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 17956 node = node.firstChild; 17957 17958 // Insert caret container after the formated node 17959 dom.insertAfter(caretContainer, formatNode); 17960 17961 // Move selection to text node 17962 selection.setCursorLocation(node, 1); 17963 } 17964 }; 17965 17966 // Checks if the parent caret container node isn't empty if that is the case it 17967 // will remove the bogus state on all children that isn't empty 17968 function unmarkBogusCaretParents() { 17969 var i, caretContainer, node; 17970 17971 caretContainer = getParentCaretContainer(selection.getStart()); 17972 if (caretContainer && !dom.isEmpty(caretContainer)) { 17973 tinymce.walk(caretContainer, function(node) { 17974 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 17975 dom.setAttrib(node, 'data-mce-bogus', null); 17976 } 17977 }, 'childNodes'); 17978 } 17979 }; 17980 17981 // Only bind the caret events once 17982 if (!self._hasCaretEvents) { 17983 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 17984 ed.onBeforeGetContent.addToTop(function() { 17985 var nodes = [], i; 17986 17987 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 17988 // Mark children 17989 i = nodes.length; 17990 while (i--) { 17991 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 17992 } 17993 } 17994 }); 17995 17996 // Remove caret container on mouse up and on key up 17997 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 17998 ed[name].addToTop(function() { 17999 removeCaretContainer(); 18000 unmarkBogusCaretParents(); 18001 }); 18002 }); 18003 18004 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 18005 ed.onKeyDown.addToTop(function(ed, e) { 18006 var keyCode = e.keyCode; 18007 18008 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 18009 removeCaretContainer(getParentCaretContainer(selection.getStart())); 18010 } 18011 18012 unmarkBogusCaretParents(); 18013 }); 18014 18015 // Remove bogus state if they got filled by contents using editor.selection.setContent 18016 selection.onSetContent.add(unmarkBogusCaretParents); 18017 18018 self._hasCaretEvents = true; 18019 } 18020 18021 // Do apply or remove caret format 18022 if (type == "apply") { 18023 applyCaretFormat(); 18024 } else { 18025 removeCaretFormat(); 18026 } 18027 }; 18028 18029 function moveStart(rng) { 18030 var container = rng.startContainer, 18031 offset = rng.startOffset, isAtEndOfText, 18032 walker, node, nodes, tmpNode; 18033 18034 // Convert text node into index if possible 18035 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18036 // Get the parent container location and walk from there 18037 offset = nodeIndex(container); 18038 container = container.parentNode; 18039 isAtEndOfText = true; 18040 } 18041 18042 // Move startContainer/startOffset in to a suitable node 18043 if (container.nodeType == 1) { 18044 nodes = container.childNodes; 18045 container = nodes[Math.min(offset, nodes.length - 1)]; 18046 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18047 18048 // If offset is at end of the parent node walk to the next one 18049 if (offset > nodes.length - 1 || isAtEndOfText) 18050 walker.next(); 18051 18052 for (node = walker.current(); node; node = walker.next()) { 18053 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18054 // IE has a "neat" feature where it moves the start node into the closest element 18055 // we can avoid this by inserting an element before it and then remove it after we set the selection 18056 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18057 node.parentNode.insertBefore(tmpNode, node); 18058 18059 // Set selection and remove tmpNode 18060 rng.setStart(node, 0); 18061 selection.setRng(rng); 18062 dom.remove(tmpNode); 18063 18064 return; 18065 } 18066 } 18067 } 18068 }; 18069 }; 18070 })(tinymce); 18071 18072 tinymce.onAddEditor.add(function(tinymce, ed) { 18073 var filters, fontSizes, dom, settings = ed.settings; 18074 18075 function replaceWithSpan(node, styles) { 18076 tinymce.each(styles, function(value, name) { 18077 if (value) 18078 dom.setStyle(node, name, value); 18079 }); 18080 18081 dom.rename(node, 'span'); 18082 }; 18083 18084 function convert(editor, params) { 18085 dom = editor.dom; 18086 18087 if (settings.convert_fonts_to_spans) { 18088 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18089 filters[node.nodeName.toLowerCase()](ed.dom, node); 18090 }); 18091 } 18092 }; 18093 18094 if (settings.inline_styles) { 18095 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18096 18097 filters = { 18098 font : function(dom, node) { 18099 replaceWithSpan(node, { 18100 backgroundColor : node.style.backgroundColor, 18101 color : node.color, 18102 fontFamily : node.face, 18103 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18104 }); 18105 }, 18106 18107 u : function(dom, node) { 18108 replaceWithSpan(node, { 18109 textDecoration : 'underline' 18110 }); 18111 }, 18112 18113 strike : function(dom, node) { 18114 replaceWithSpan(node, { 18115 textDecoration : 'line-through' 18116 }); 18117 } 18118 }; 18119 18120 ed.onPreProcess.add(convert); 18121 ed.onSetContent.add(convert); 18122 18123 ed.onInit.add(function() { 18124 ed.selection.onSetContent.add(convert); 18125 }); 18126 } 18127 }); 18128 18129 (function(tinymce) { 18130 var TreeWalker = tinymce.dom.TreeWalker; 18131 18132 tinymce.EnterKey = function(editor) { 18133 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18134 18135 function handleEnterKey(evt) { 18136 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, 18137 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18138 18139 // Returns true if the block can be split into two blocks or not 18140 function canSplitBlock(node) { 18141 return node && 18142 dom.isBlock(node) && 18143 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18144 !/^(fixed|absolute)/i.test(node.style.position) && 18145 dom.getContentEditable(node) !== "true"; 18146 }; 18147 18148 // Renders empty block on IE 18149 function renderBlockOnIE(block) { 18150 var oldRng; 18151 18152 if (tinymce.isIE && dom.isBlock(block)) { 18153 oldRng = selection.getRng(); 18154 block.appendChild(dom.create('span', null, '\u00a0')); 18155 selection.select(block); 18156 block.lastChild.outerHTML = ''; 18157 selection.setRng(oldRng); 18158 } 18159 }; 18160 18161 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18162 function trimInlineElementsOnLeftSideOfBlock(block) { 18163 var node = block, firstChilds = [], i; 18164 18165 // Find inner most first child ex: <p><i><b>*</b></i></p> 18166 while (node = node.firstChild) { 18167 if (dom.isBlock(node)) { 18168 return; 18169 } 18170 18171 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18172 firstChilds.push(node); 18173 } 18174 } 18175 18176 i = firstChilds.length; 18177 while (i--) { 18178 node = firstChilds[i]; 18179 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18180 dom.remove(node); 18181 } 18182 } 18183 }; 18184 18185 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18186 function moveToCaretPosition(root) { 18187 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18188 18189 rng = dom.createRng(); 18190 18191 if (root.hasChildNodes()) { 18192 walker = new TreeWalker(root, root); 18193 18194 while (node = walker.current()) { 18195 if (node.nodeType == 3) { 18196 rng.setStart(node, 0); 18197 rng.setEnd(node, 0); 18198 break; 18199 } 18200 18201 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18202 rng.setStartBefore(node); 18203 rng.setEndBefore(node); 18204 break; 18205 } 18206 18207 lastNode = node; 18208 node = walker.next(); 18209 } 18210 18211 if (!node) { 18212 rng.setStart(lastNode, 0); 18213 rng.setEnd(lastNode, 0); 18214 } 18215 } else { 18216 if (root.nodeName == 'BR') { 18217 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18218 // Trick on older IE versions to render the caret before the BR between two lists 18219 if (!documentMode || documentMode < 9) { 18220 tempElm = dom.create('br'); 18221 root.parentNode.insertBefore(tempElm, root); 18222 } 18223 18224 rng.setStartBefore(root); 18225 rng.setEndBefore(root); 18226 } else { 18227 rng.setStartAfter(root); 18228 rng.setEndAfter(root); 18229 } 18230 } else { 18231 rng.setStart(root, 0); 18232 rng.setEnd(root, 0); 18233 } 18234 } 18235 18236 selection.setRng(rng); 18237 18238 // Remove tempElm created for old IE:s 18239 dom.remove(tempElm); 18240 18241 viewPort = dom.getViewPort(editor.getWin()); 18242 18243 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs 18244 y = dom.getPos(root).y; 18245 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18246 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18247 } 18248 }; 18249 18250 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18251 // This function will also copy any text formatting from the parent block and add it to the new one 18252 function createNewBlock(name) { 18253 var node = container, block, clonedNode, caretNode; 18254 18255 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18256 caretNode = block; 18257 18258 // Clone any parent styles 18259 if (settings.keep_styles !== false) { 18260 do { 18261 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18262 clonedNode = node.cloneNode(false); 18263 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18264 18265 if (block.hasChildNodes()) { 18266 clonedNode.appendChild(block.firstChild); 18267 block.appendChild(clonedNode); 18268 } else { 18269 caretNode = clonedNode; 18270 block.appendChild(clonedNode); 18271 } 18272 } 18273 } while (node = node.parentNode); 18274 } 18275 18276 // BR is needed in empty blocks on non IE browsers 18277 if (!tinymce.isIE) { 18278 caretNode.innerHTML = '<br>'; 18279 } 18280 18281 return block; 18282 }; 18283 18284 // Returns true/false if the caret is at the start/end of the parent block element 18285 function isCaretAtStartOrEndOfBlock(start) { 18286 var walker, node, name; 18287 18288 // Caret is in the middle of a text node like "a|b" 18289 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18290 return false; 18291 } 18292 18293 // If after the last element in block node edge case for #5091 18294 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18295 return true; 18296 } 18297 18298 // If the caret if before the first element in parentBlock 18299 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18300 return true; 18301 } 18302 18303 // Caret can be before/after a table 18304 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18305 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18306 } 18307 18308 // Walk the DOM and look for text nodes or non empty elements 18309 walker = new TreeWalker(container, parentBlock); 18310 18311 // If caret is in beginning or end of a text block then jump to the next/previous node 18312 if (container.nodeType == 3) { 18313 if (start && offset == 0) { 18314 walker.prev(); 18315 } else if (!start && offset == container.nodeValue.length) { 18316 walker.next(); 18317 } 18318 } 18319 18320 while (node = walker.current()) { 18321 if (node.nodeType === 1) { 18322 // Ignore bogus elements 18323 if (!node.getAttribute('data-mce-bogus')) { 18324 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18325 name = node.nodeName.toLowerCase(); 18326 if (nonEmptyElementsMap[name] && name !== 'br') { 18327 return false; 18328 } 18329 } 18330 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18331 return false; 18332 } 18333 18334 if (start) { 18335 walker.prev(); 18336 } else { 18337 walker.next(); 18338 } 18339 } 18340 18341 return true; 18342 }; 18343 18344 // Wraps any text nodes or inline elements in the specified forced root block name 18345 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18346 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18347 18348 // Not in a block element or in a table cell or caption 18349 parentBlock = dom.getParent(container, dom.isBlock); 18350 if (!parentBlock || !canSplitBlock(parentBlock)) { 18351 parentBlock = parentBlock || editableRoot; 18352 18353 if (!parentBlock.hasChildNodes()) { 18354 newBlock = dom.create(blockName); 18355 parentBlock.appendChild(newBlock); 18356 rng.setStart(newBlock, 0); 18357 rng.setEnd(newBlock, 0); 18358 return newBlock; 18359 } 18360 18361 // Find parent that is the first child of parentBlock 18362 node = container; 18363 while (node.parentNode != parentBlock) { 18364 node = node.parentNode; 18365 } 18366 18367 // Loop left to find start node start wrapping at 18368 while (node && !dom.isBlock(node)) { 18369 startNode = node; 18370 node = node.previousSibling; 18371 } 18372 18373 if (startNode) { 18374 newBlock = dom.create(blockName); 18375 startNode.parentNode.insertBefore(newBlock, startNode); 18376 18377 // Start wrapping until we hit a block 18378 node = startNode; 18379 while (node && !dom.isBlock(node)) { 18380 next = node.nextSibling; 18381 newBlock.appendChild(node); 18382 node = next; 18383 } 18384 18385 // Restore range to it's past location 18386 rng.setStart(container, offset); 18387 rng.setEnd(container, offset); 18388 } 18389 } 18390 18391 return container; 18392 }; 18393 18394 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18395 function handleEmptyListItem() { 18396 function isFirstOrLastLi(first) { 18397 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18398 18399 // Find first/last element since there might be whitespace there 18400 while (node) { 18401 if (node.nodeType == 1) { 18402 break; 18403 } 18404 18405 node = node[first ? 'nextSibling' : 'previousSibling']; 18406 } 18407 18408 return node === parentBlock; 18409 }; 18410 18411 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18412 18413 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18414 // Is first and last list item then replace the OL/UL with a text block 18415 dom.replace(newBlock, containerBlock); 18416 } else if (isFirstOrLastLi(true)) { 18417 // First LI in list then remove LI and add text block before list 18418 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18419 } else if (isFirstOrLastLi()) { 18420 // Last LI in list then temove LI and add text block after list 18421 dom.insertAfter(newBlock, containerBlock); 18422 renderBlockOnIE(newBlock); 18423 } else { 18424 // Middle LI in list the split the list and insert a text block in the middle 18425 // Extract after fragment and insert it after the current block 18426 tmpRng = rng.cloneRange(); 18427 tmpRng.setStartAfter(parentBlock); 18428 tmpRng.setEndAfter(containerBlock); 18429 fragment = tmpRng.extractContents(); 18430 dom.insertAfter(fragment, containerBlock); 18431 dom.insertAfter(newBlock, containerBlock); 18432 } 18433 18434 dom.remove(parentBlock); 18435 moveToCaretPosition(newBlock); 18436 undoManager.add(); 18437 }; 18438 18439 // Walks the parent block to the right and look for BR elements 18440 function hasRightSideBr() { 18441 var walker = new TreeWalker(container, parentBlock), node; 18442 18443 while (node = walker.current()) { 18444 if (node.nodeName == 'BR') { 18445 return true; 18446 } 18447 18448 node = walker.next(); 18449 } 18450 } 18451 18452 // Inserts a BR element if the forced_root_block option is set to false or empty string 18453 function insertBr() { 18454 var brElm, extraBr; 18455 18456 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18457 // Insert extra BR element at the end block elements 18458 if (!tinymce.isIE && !hasRightSideBr()) { 18459 brElm = dom.create('br') 18460 rng.insertNode(brElm); 18461 rng.setStartAfter(brElm); 18462 rng.setEndAfter(brElm); 18463 extraBr = true; 18464 } 18465 } 18466 18467 brElm = dom.create('br'); 18468 rng.insertNode(brElm); 18469 18470 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18471 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18472 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18473 } 18474 18475 if (!extraBr) { 18476 rng.setStartAfter(brElm); 18477 rng.setEndAfter(brElm); 18478 } else { 18479 rng.setStartBefore(brElm); 18480 rng.setEndBefore(brElm); 18481 } 18482 18483 selection.setRng(rng); 18484 undoManager.add(); 18485 }; 18486 18487 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18488 function trimLeadingLineBreaks(node) { 18489 do { 18490 if (node.nodeType === 3) { 18491 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18492 } 18493 18494 node = node.firstChild; 18495 } while (node); 18496 }; 18497 18498 function getEditableRoot(node) { 18499 var root = dom.getRoot(), parent, editableRoot; 18500 18501 // Get all parents until we hit a non editable parent or the root 18502 parent = node; 18503 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18504 if (dom.getContentEditable(parent) === "true") { 18505 editableRoot = parent; 18506 } 18507 18508 parent = parent.parentNode; 18509 } 18510 18511 return parent !== root ? editableRoot : root; 18512 }; 18513 18514 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18515 function addBrToBlockIfNeeded(block) { 18516 var lastChild; 18517 18518 // IE will render the blocks correctly other browsers needs a BR 18519 if (!tinymce.isIE) { 18520 block.normalize(); // Remove empty text nodes that got left behind by the extract 18521 18522 // Check if the block is empty or contains a floated last child 18523 lastChild = block.lastChild; 18524 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18525 dom.add(block, 'br'); 18526 } 18527 } 18528 }; 18529 18530 // Delete any selected contents 18531 if (!rng.collapsed) { 18532 editor.execCommand('Delete'); 18533 return; 18534 } 18535 18536 // Event is blocked by some other handler for example the lists plugin 18537 if (evt.isDefaultPrevented()) { 18538 return; 18539 } 18540 18541 // Setup range items and newBlockName 18542 container = rng.startContainer; 18543 offset = rng.startOffset; 18544 newBlockName = settings.forced_root_block; 18545 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18546 documentMode = dom.doc.documentMode; 18547 18548 // Resolve node index 18549 if (container.nodeType == 1 && container.hasChildNodes()) { 18550 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18551 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18552 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18553 offset = container.nodeValue.length; 18554 } else { 18555 offset = 0; 18556 } 18557 } 18558 18559 // Get editable root node normaly the body element but sometimes a div or span 18560 editableRoot = getEditableRoot(container); 18561 18562 // If there is no editable root then enter is done inside a contentEditable false element 18563 if (!editableRoot) { 18564 return; 18565 } 18566 18567 undoManager.beforeChange(); 18568 18569 // If editable root isn't block nor the root of the editor 18570 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18571 if (!newBlockName || evt.shiftKey) { 18572 insertBr(); 18573 } 18574 18575 return; 18576 } 18577 18578 // Wrap the current node and it's sibling in a default block if it's needed. 18579 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18580 // This won't happen if root blocks are disabled or the shiftKey is pressed 18581 if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) { 18582 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18583 } 18584 18585 // Find parent block and setup empty block paddings 18586 parentBlock = dom.getParent(container, dom.isBlock); 18587 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18588 18589 // Setup block names 18590 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18591 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18592 18593 // Handle enter inside an empty list item 18594 if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) { 18595 // Let the list plugin or browser handle nested lists for now 18596 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 18597 return false; 18598 } 18599 18600 handleEmptyListItem(); 18601 return; 18602 } 18603 18604 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 18605 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 18606 if (!evt.shiftKey) { 18607 insertBr(); 18608 return; 18609 } 18610 } else { 18611 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 18612 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) { 18613 insertBr(); 18614 return; 18615 } 18616 } 18617 18618 // Default block name if it's not configured 18619 newBlockName = newBlockName || 'P'; 18620 18621 // Insert new block before/after the parent block depending on caret location 18622 if (isCaretAtStartOrEndOfBlock()) { 18623 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 18624 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 18625 newBlock = createNewBlock(newBlockName); 18626 } else { 18627 newBlock = createNewBlock(); 18628 } 18629 18630 // Split the current container block element if enter is pressed inside an empty inner block element 18631 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 18632 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 18633 newBlock = dom.split(containerBlock, parentBlock); 18634 } else { 18635 dom.insertAfter(newBlock, parentBlock); 18636 } 18637 18638 moveToCaretPosition(newBlock); 18639 } else if (isCaretAtStartOrEndOfBlock(true)) { 18640 // Insert new block before 18641 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 18642 renderBlockOnIE(newBlock); 18643 } else { 18644 // Extract after fragment and insert it after the current block 18645 tmpRng = rng.cloneRange(); 18646 tmpRng.setEndAfter(parentBlock); 18647 fragment = tmpRng.extractContents(); 18648 trimLeadingLineBreaks(fragment); 18649 newBlock = fragment.firstChild; 18650 dom.insertAfter(fragment, parentBlock); 18651 trimInlineElementsOnLeftSideOfBlock(newBlock); 18652 addBrToBlockIfNeeded(parentBlock); 18653 moveToCaretPosition(newBlock); 18654 } 18655 18656 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 18657 undoManager.add(); 18658 } 18659 18660 editor.onKeyDown.add(function(ed, evt) { 18661 if (evt.keyCode == 13) { 18662 if (handleEnterKey(evt) !== false) { 18663 evt.preventDefault(); 18664 } 18665 } 18666 }); 18667 }; 18668 })(tinymce); 18669 18670